diff -Nru apache2-2.2.22/debian/changelog apache2-2.2.22/debian/changelog --- apache2-2.2.22/debian/changelog 2016-07-14 12:50:27.000000000 +0000 +++ apache2-2.2.22/debian/changelog 2019-04-09 20:02:57.000000000 +0000 @@ -1,3 +1,83 @@ +apache2 (2.2.22-1ubuntu1.15) precise-security; urgency=medium + + [ Marc Deslauriers ] + * SECURITY UPDATE: DoS via missing header with AuthLDAPCharsetConfig + - debian/patches/CVE-2017-15710.patch: fix language long names + detection as short name in modules/aaa/mod_authnz_ldap.c. + - CVE-2017-15710 + * SECURITY UPDATE: DoS via specially-crafted request + - debian/patches/CVE-2018-1301.patch: ensure that read lines are NUL + terminated on any error, not only on buffer full in + server/protocol.c. + - CVE-2018-1301 + * SECURITY UPDATE: insecure nonce generation + - debian/patches/CVE-2018-1312-*.patch: actually use the secret when + generating nonces in modules/aaa/mod_auth_digest.c. + - CVE-2018-1312 + * SECURITY UPDATE: mod_auth_digest access control bypass + - debian/patches/CVE-2019-0217.patch: fix a race condition in + modules/aaa/mod_auth_digest.c. + - CVE-2019-0217 + + -- Leonidas S. Barbosa Tue, 09 Apr 2019 12:48:30 -0300 + +apache2 (2.2.22-1ubuntu1.14) precise-security; urgency=medium + + [ Marc Deslauriers ] + * SECURITY UPDATE: optionsbleed information leak + - debian/patches/CVE-2017-9798.patch: disallow method registration + at run time in server/core.c. + - CVE-2017-9798 + + -- Leonidas S. Barbosa Tue, 17 Oct 2017 18:54:42 -0300 + +apache2 (2.2.22-1ubuntu1.13) precise-security; urgency=medium + + [ Marc Deslauriers ] + * SECURITY UPDATE: uninitialized memory reflection in mod_auth_digest + - debian/patches/CVE-2017-9788.patch: correct string scope in + modules/aaa/mod_auth_digest.c. + - CVE-2017-9788 + + -- Leonidas S. Barbosa Tue, 01 Aug 2017 11:54:21 -0300 + +apache2 (2.2.22-1ubuntu1.12) precise-security; urgency=medium + + * SECURITY UPDATE: authentication bypass in ap_get_basic_auth_pw() + - debian/patches/CVE-2017-3167.patch: deprecate and replace + ap_get_basic_auth_pw in include/ap_mm.h, include/http_protocol.h, + server/protocol.c, server/request.c. + - CVE-2017-3167 + * SECURITY UPDATE: NULL pointer deref in ap_hook_process_connection() + - debian/patches/CVE-2017-3169.patch: fix ctx passed to + ssl_io_filter_error() in modules/ssl/ssl_engine_io.c. + - CVE-2017-3169 + * SECURITY UDPATE: denial of service and possible incorrect value return + in HTTP strict parsing changes + - debian/patches/CVE-2017-7668.patch: short-circuit on NULL in + server/util.c. + - CVE-2017-7668 + * SECURITY UPDATE: mod_mime DoS via crafted Content-Type response header + - debian/patches/CVE-2017-7679.patch: fix quoted pair scanning in + modules/http/mod_mime.c. + - CVE-2017-7679 + * SECURITY UPDATE: response splitting and cache pollution issue via + imcomplete RCF7230 HTTP request grammar enforcing + - debian/patches/CVE-2016-8743*.patch: enforce stricter parsing in + include/http_core.h, include/http_protocal.h, include/httpd.h, + modules/http/http_filters.c, server/core.c, server/gen_test_char.c, + server/protocol.c, server/util.c, server/vhost.c. + This patch set were applied from Wheezy. Patch CVE-2016-8743-4.patch + fix a possible regression. Thanks Antoine Beaupre. + - CVE-2016-8743 + * WARNING: The fix for CVE-2016-8743 introduces a behavioural change and may + introduce compatibility issues with clients that do not strictly + follow specifications. A new configuration directive, + "HttpProtocolOptions Unsafe" can be used to re-enable some of the less + strict parsing restrictions, at the expense of security. + + -- Leonidas S. Barbosa Fri, 28 Jul 2017 20:27:54 -0300 + apache2 (2.2.22-1ubuntu1.11) precise-security; urgency=medium * SECURITY UPDATE: proxy request header vulnerability (httpoxy) diff -Nru apache2-2.2.22/debian/patches/0001-CVE-2017-15710.patch apache2-2.2.22/debian/patches/0001-CVE-2017-15710.patch --- apache2-2.2.22/debian/patches/0001-CVE-2017-15710.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/0001-CVE-2017-15710.patch 2019-04-09 18:45:12.000000000 +0000 @@ -0,0 +1,31 @@ +Description: fix DoS via missing header with AuthLDAPCharsetConfig +Origin: upstream, http://svn.apache.org/viewvc?view=revision&revision=1824456 + +--- + modules/aaa/mod_authnz_ldap.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/modules/aaa/mod_authnz_ldap.c b/modules/aaa/mod_authnz_ldap.c +index ce1af3d..8ef53ef 100644 +--- a/modules/aaa/mod_authnz_ldap.c ++++ b/modules/aaa/mod_authnz_ldap.c +@@ -117,9 +117,13 @@ static char* derive_codepage_from_lang (apr_pool_t *p, char *language) + + charset = (char*) apr_hash_get(charset_conversions, language, APR_HASH_KEY_STRING); + +- if (!charset) { +- language[2] = '\0'; +- charset = (char*) apr_hash_get(charset_conversions, language, APR_HASH_KEY_STRING); ++ /* ++ * Test if language values like 'en-US' return a match from the charset ++ * conversion map when shortened to 'en'. ++ */ ++ if (!charset && strlen(language) > 3 && language[2] == '-') { ++ char *language_short = apr_pstrndup(p, language, 2); ++ charset = (char*) apr_hash_get(charset_conversions, language_short, APR_HASH_KEY_STRING); + } + + if (charset) { +-- +2.7.4 + diff -Nru apache2-2.2.22/debian/patches/0002-CVE-2018-1301.patch apache2-2.2.22/debian/patches/0002-CVE-2018-1301.patch --- apache2-2.2.22/debian/patches/0002-CVE-2018-1301.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/0002-CVE-2018-1301.patch 2019-04-09 18:45:10.000000000 +0000 @@ -0,0 +1,207 @@ +Description: fix DoS via specially-crafted request +Origin: upstream, https://svn.apache.org/viewvc?view=revision&revision=1824469 + +--- + server/protocol.c | 76 ++++++++++++++++++++++++++++++------------------------- + 1 file changed, 42 insertions(+), 34 deletions(-) + +diff --git a/server/protocol.c b/server/protocol.c +index fc5ad77..1d44236 100644 +--- a/server/protocol.c ++++ b/server/protocol.c +@@ -220,6 +220,11 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + int fold = flags & AP_GETLINE_FOLD; + int crlf = flags & AP_GETLINE_CRLF; + ++ if (!n) { ++ /* Needs room for NUL byte at least */ ++ return APR_BADARG; ++ } ++ + /* + * Initialize last_char as otherwise a random value will be compared + * against APR_ASCII_LF at the end of the loop if bb only contains +@@ -233,14 +238,15 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_GETLINE, + APR_BLOCK_READ, 0); + if (rv != APR_SUCCESS) { +- return rv; ++ goto cleanup; + } + + /* 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; ++ rv = APR_EGENERAL; ++ goto cleanup; + } + + for (e = APR_BRIGADE_FIRST(bb); +@@ -258,7 +264,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + + rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { +- return rv; ++ goto cleanup; + } + + if (len == 0) { +@@ -271,17 +277,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + + /* Would this overrun our buffer? If so, we'll die. */ + if (n < bytes_handled + len) { +- *read = bytes_handled; +- if (*s) { +- /* ensure this string is NUL terminated */ +- if (bytes_handled > 0) { +- (*s)[bytes_handled-1] = '\0'; +- } +- else { +- (*s)[0] = '\0'; +- } +- } +- return APR_ENOSPC; ++ rv = APR_ENOSPC; ++ goto cleanup; + } + + /* Do we have to handle the allocation ourselves? */ +@@ -289,7 +286,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + /* We'll assume the common case where one bucket is enough. */ + if (!*s) { + current_alloc = len; +- *s = apr_palloc(r->pool, current_alloc); ++ *s = apr_palloc(r->pool, current_alloc + 1); + } + else if (bytes_handled + len > current_alloc) { + /* Increase the buffer size */ +@@ -300,7 +297,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + new_size = (bytes_handled + len) * 2; + } + +- new_buffer = apr_palloc(r->pool, new_size); ++ new_buffer = apr_palloc(r->pool, new_size + 1); + + /* Copy what we already had. */ + memcpy(new_buffer, *s, bytes_handled); +@@ -324,19 +321,15 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + } + } + +- 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; ++ /* Now 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) { + last_char--; + } +- *last_char = '\0'; ++ else if (crlf) { ++ rv = APR_EINVAL; ++ goto cleanup; ++ } + bytes_handled = last_char - *s; + + /* If we're folding, we have more work to do. +@@ -356,7 +349,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_SPECULATIVE, + APR_BLOCK_READ, 1); + if (rv != APR_SUCCESS) { +- return rv; ++ goto cleanup; + } + + if (APR_BRIGADE_EMPTY(bb)) { +@@ -373,7 +366,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + rv = apr_bucket_read(e, &str, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + apr_brigade_cleanup(bb); +- return rv; ++ goto cleanup; + } + + /* Found one, so call ourselves again to get the next line. +@@ -390,10 +383,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + if (c == APR_ASCII_BLANK || c == APR_ASCII_TAB) { + /* Do we have enough space? We may be full now. */ + if (bytes_handled >= n) { +- *read = n; +- /* ensure this string is terminated */ +- (*s)[n-1] = '\0'; +- return APR_ENOSPC; ++ rv = APR_ENOSPC; ++ goto cleanup; + } + else { + apr_size_t next_size, next_len; +@@ -406,7 +397,6 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + tmp = NULL; + } + else { +- /* We're null terminated. */ + tmp = last_char; + } + +@@ -415,7 +405,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + rv = ap_rgetline_core(&tmp, next_size, + &next_len, r, 0, bb); + if (rv != APR_SUCCESS) { +- return rv; ++ goto cleanup; + } + + if (do_alloc && next_len > 0) { +@@ -429,7 +419,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + memcpy(new_buffer, *s, bytes_handled); + + /* copy the new line, including the trailing null */ +- memcpy(new_buffer + bytes_handled, tmp, next_len + 1); ++ memcpy(new_buffer + bytes_handled, tmp, next_len); + *s = new_buffer; + } + +@@ -442,8 +432,21 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + } + } + } ++ ++cleanup: ++ if (bytes_handled >= n) { ++ bytes_handled = n - 1; ++ } ++ if (*s) { ++ /* ensure the string is NUL terminated */ ++ (*s)[bytes_handled] = '\0'; ++ } + *read = bytes_handled; + ++ if (rv != APR_SUCCESS) { ++ return rv; ++ } ++ + /* PR#43039: We shouldn't accept NULL bytes within the line */ + if (strlen(*s) < bytes_handled) { + return APR_EINVAL; +@@ -482,6 +485,11 @@ AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags) + apr_size_t len; + apr_bucket_brigade *tmp_bb; + ++ if (n < 1) { ++ /* Can't work since we always NUL terminate */ ++ return -1; ++ } ++ + tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + rv = ap_rgetline(&tmp_s, n, &len, r, flags, tmp_bb); + apr_brigade_destroy(tmp_bb); +-- +2.7.4 + diff -Nru apache2-2.2.22/debian/patches/0003-CVE-2018-1312-pre.patch apache2-2.2.22/debian/patches/0003-CVE-2018-1312-pre.patch --- apache2-2.2.22/debian/patches/0003-CVE-2018-1312-pre.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/0003-CVE-2018-1312-pre.patch 2019-04-09 18:45:12.000000000 +0000 @@ -0,0 +1,67 @@ +From f8c7ff9c993fdcca4967f2de7ad584c907f46317 Mon Sep 17 00:00:00 2001 +From: Daniel Earl Poirier +Date: Thu, 10 Sep 2009 12:12:58 +0000 +Subject: [PATCH] Fail server startup when mod_auth_digest is unable to provide + the security checks configured. + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@813396 13f79535-47bb-0310-9956-ffa450edef68 + +--- + modules/aaa/mod_auth_digest.c | 26 +++++++++++++++----------- + 1 file changed, 15 insertions(+), 11 deletions(-) + +diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c +index b527ed3..7b02ec1 100644 +--- a/modules/aaa/mod_auth_digest.c ++++ b/modules/aaa/mod_auth_digest.c +@@ -547,11 +547,13 @@ static const char *set_nonce_format(cmd_parms *cmd, void *config, + + static const char *set_nc_check(cmd_parms *cmd, void *config, int flag) + { +- if (flag && !client_shm) +- ap_log_error(APLOG_MARK, APLOG_WARNING, 0, +- cmd->server, "Digest: WARNING: nonce-count checking " ++#if !APR_HAS_SHARED_MEMORY ++ if (flag) { ++ return "AuthDigestNcCheck: ERROR: nonce-count checking " + "is not supported on platforms without shared-memory " +- "support - disabling check"); ++ "support"; ++ } ++#endif + + ((digest_config_rec *) config)->check_nc = flag; + return NULL; +@@ -560,13 +562,8 @@ static const char *set_nc_check(cmd_parms *cmd, void *config, int flag) + static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg) + { + if (!strcasecmp(alg, "MD5-sess")) { +- if (!client_shm) { +- ap_log_error(APLOG_MARK, APLOG_WARNING, 0, +- cmd->server, "Digest: WARNING: algorithm `MD5-sess' " +- "is not supported on platforms without shared-memory " +- "support - reverting to MD5"); +- alg = "MD5"; +- } ++ return "AuthDigestAlgorithm: ERROR: algorithm `MD5-sess' " ++ "is not fully implemented"; + } + else if (strcasecmp(alg, "MD5")) { + return apr_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL); +@@ -1381,6 +1378,13 @@ static int check_nc(const request_rec *r, const digest_header_rec *resp, + const char *snc = resp->nonce_count; + char *endptr; + ++ if (conf->check_nc && !client_shm) { ++ /* Shouldn't happen, but just in case... */ ++ ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, ++ "Digest: cannot check nonce count without shared memory"); ++ return OK; ++ } ++ + if (!conf->check_nc || !client_shm) { + return OK; + } +-- +2.7.4 + diff -Nru apache2-2.2.22/debian/patches/0004-CVE-2018-1312-1.patch apache2-2.2.22/debian/patches/0004-CVE-2018-1312-1.patch --- apache2-2.2.22/debian/patches/0004-CVE-2018-1312-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/0004-CVE-2018-1312-1.patch 2019-04-09 18:45:12.000000000 +0000 @@ -0,0 +1,143 @@ +From 4260611634e79682421db53470004ee3d2d8cbc5 Mon Sep 17 00:00:00 2001 +From: Stefan Fritsch +Date: Wed, 12 Jun 2013 19:34:19 +0000 +Subject: [PATCH] Actually use the secret when generating nonces. + +This change may cause problems if used with round robin load balancers. +Before it is backported, we should add a directive to use a user specified +secret. + +PR: 54637 + + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1492395 13f79535-47bb-0310-9956-ffa450edef68 + +--- + modules/aaa/mod_auth_digest.c | 57 ++++++++++++++++++++++++++++++++++--------- + 1 file changed, 46 insertions(+), 11 deletions(-) + +diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c +index 7b02ec1..e5d834a 100644 +--- a/modules/aaa/mod_auth_digest.c ++++ b/modules/aaa/mod_auth_digest.c +@@ -113,7 +113,8 @@ typedef struct digest_config_struct { + #define NONCE_HASH_LEN (2*APR_SHA1_DIGESTSIZE) + #define NONCE_LEN (int )(NONCE_TIME_LEN + NONCE_HASH_LEN) + +-#define SECRET_LEN 20 ++#define SECRET_LEN 20 ++#define POOL_USERDATA_ID "mod_auth_digest" + + + /* client list definitions */ +@@ -171,7 +172,7 @@ typedef union time_union { + unsigned char arr[sizeof(apr_time_t)]; + } time_rec; + +-static unsigned char secret[SECRET_LEN]; ++static unsigned char *secret; + + /* client-list, opaque, and one-time-nonce stuff */ + +@@ -222,17 +223,30 @@ static apr_status_t cleanup_tables(void *not_used) + return APR_SUCCESS; + } + +-static apr_status_t initialize_secret(server_rec *s) ++/* ++ * @param pool pool for userdata ++ * @param s server rec for logging, may be NULL ++ */ ++static apr_status_t initialize_secret(apr_pool_t *pool, server_rec *s) + { + apr_status_t status; ++ void *userdata; ++ ++ apr_pool_userdata_get(&userdata, POOL_USERDATA_ID, pool); ++ if (userdata != NULL) { ++ secret = userdata; ++ return APR_SUCCESS; ++ } + + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, +- "Digest: generating secret for digest authentication ..."); ++ "Digest: generating secret for digest authentication"); ++ ++ secret = apr_palloc(pool, SECRET_LEN); + + #if APR_HAS_RANDOM +- status = apr_generate_random_bytes(secret, sizeof(secret)); ++ status = apr_generate_random_bytes(secret, SECRET_LEN); + #else +-#error APR random number support is missing; you probably need to install the truerand library. ++#error APR random number support is missing + #endif + + if (status != APR_SUCCESS) { +@@ -240,12 +254,14 @@ static apr_status_t initialize_secret(server_rec *s) + ap_log_error(APLOG_MARK, APLOG_CRIT, status, s, + "Digest: error generating secret: %s", + apr_strerror(status, buf, sizeof(buf))); ++ secret = NULL; + return status; + } + +- ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "Digest: done"); ++ apr_pool_userdata_set(secret, POOL_USERDATA_ID, apr_pool_cleanup_null, ++ pool); + +- return APR_SUCCESS; ++ return status; + } + + static void log_error_and_cleanup(char *msg, apr_status_t sts, server_rec *s) +@@ -349,9 +365,13 @@ static int initialize_module(apr_pool_t *p, apr_pool_t *plog, + apr_pool_cleanup_null, s->process->pool); + return OK; + } +- if (initialize_secret(s) != APR_SUCCESS) { ++ /* ++ * If we haven't initialized the secret yet, we need to do it now in case ++ * the module is used in .htaccess. ++ */ ++ if (secret == NULL ++ && initialize_secret(s->process->pool, s) != APR_SUCCESS) + return !OK; +- } + + #if APR_HAS_SHARED_MEMORY + /* Note: this stuff is currently fixed for the lifetime of the server, +@@ -421,6 +441,21 @@ static void *create_digest_dir_config(apr_pool_t *p, char *dir) + static const char *set_realm(cmd_parms *cmd, void *config, const char *realm) + { + digest_config_rec *conf = (digest_config_rec *) config; ++ int i; ++ ++ /* pass NULL because cmd->server may not have a valid log config yet */ ++ if (secret == NULL ++ && initialize_secret(cmd->server->process->pool, NULL) != APR_SUCCESS) ++ return "Could not get random numbers for secret"; ++ ++#ifdef AP_DEBUG ++ /* check that we got random numbers */ ++ for (i = 0; i < SECRET_LEN; i++) { ++ if (secret[i] != 0) ++ break; ++ } ++ ap_assert(i < SECRET_LEN); ++#endif + + /* The core already handles the realm, but it's just too convenient to + * grab it ourselves too and cache some setups. However, we need to +@@ -434,7 +469,7 @@ static const char *set_realm(cmd_parms *cmd, void *config, const char *realm) + * and directives outside a virtual host section) + */ + apr_sha1_init(&conf->nonce_ctx); +- apr_sha1_update_binary(&conf->nonce_ctx, secret, sizeof(secret)); ++ apr_sha1_update_binary(&conf->nonce_ctx, secret, SECRET_LEN); + apr_sha1_update_binary(&conf->nonce_ctx, (const unsigned char *) realm, + strlen(realm)); + +-- +2.7.4 + diff -Nru apache2-2.2.22/debian/patches/0005-CVE-2018-1312-2.patch apache2-2.2.22/debian/patches/0005-CVE-2018-1312-2.patch --- apache2-2.2.22/debian/patches/0005-CVE-2018-1312-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/0005-CVE-2018-1312-2.patch 2019-04-09 18:45:11.000000000 +0000 @@ -0,0 +1,294 @@ +From 0320de2034beedaa22ebfb50c112a3baa4b3cd20 Mon Sep 17 00:00:00 2001 +From: Stefan Fritsch +Date: Sun, 31 Mar 2013 20:38:17 +0000 +Subject: [PATCH] Remove partial non-working implementation of MD5-sess and + qop=auth-int. + +If anyone wants to finish the code, it can be retrieved from svn history. +Remove some obsolete references to the truerand library. + + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1463049 13f79535-47bb-0310-9956-ffa450edef68 +--- + modules/aaa/mod_auth_digest.c | 166 ++++-------------------------------------- + 1 file changed, 14 insertions(+), 152 deletions(-) + +diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c +index e5d834a..e315bd5 100644 +--- a/modules/aaa/mod_auth_digest.c ++++ b/modules/aaa/mod_auth_digest.c +@@ -26,20 +26,13 @@ + * reports to the Apache bug-database, or send them directly to me + * at ronald@innovation.ch. + * +- * Requires either /dev/random (or equivalent) or the truerand library, +- * available for instance from +- * ftp://research.att.com/dist/mab/librand.shar +- * + * Open Issues: + * - qop=auth-int (when streams and trailer support available) + * - nonce-format configurability + * - Proxy-Authorization-Info header is set by this module, but is + * currently ignored by mod_proxy (needs patch to mod_proxy) +- * - generating the secret takes a while (~ 8 seconds) if using the +- * truerand library + * - The source of the secret should be run-time directive (with server +- * scope: RSRC_CONF). However, that could be tricky when trying to +- * choose truerand vs. file... ++ * scope: RSRC_CONF) + * - shared-mem not completely tested yet. Seems to work ok for me, + * but... (definitely won't work on Windoze) + * - Sharing a realm among multiple servers has following problems: +@@ -52,6 +45,8 @@ + * captures a packet sent to one server and sends it to another + * one. Should we add "AuthDigestNcCheck Strict"? + * - expired nonces give amaya fits. ++ * - MD5-sess and auth-int are not yet implemented. An incomplete ++ * implementation has been removed and can be retrieved from svn history. + */ + + #include "apr_sha1.h" +@@ -95,7 +90,6 @@ typedef struct digest_config_struct { + char **qop_list; + apr_sha1_ctx_t nonce_ctx; + apr_time_t nonce_lifetime; +- const char *nonce_format; + int check_nc; + const char *algorithm; + char *uri_list; +@@ -123,7 +117,6 @@ typedef struct hash_entry { + unsigned long key; /* the key for this entry */ + struct hash_entry *next; /* next entry in the bucket */ + unsigned long nonce_count; /* for nonce-count checking */ +- char ha1[2*APR_MD5_DIGESTSIZE+1]; /* for algorithm=MD5-sess */ + char last_nonce[NONCE_LEN+1]; /* for one-time nonce's */ + } client_entry; + +@@ -267,8 +260,8 @@ static apr_status_t initialize_secret(apr_pool_t *pool, server_rec *s) + static void log_error_and_cleanup(char *msg, apr_status_t sts, server_rec *s) + { + ap_log_error(APLOG_MARK, APLOG_ERR, sts, s, +- "Digest: %s - all nonce-count checking, one-time nonces, and " +- "MD5-sess algorithm disabled", msg); ++ "Digest: %s - all nonce-count checking and one-time nonces" ++ "disabled", msg); + + cleanup_tables(NULL); + } +@@ -576,8 +569,7 @@ static const char *set_nonce_lifetime(cmd_parms *cmd, void *config, + static const char *set_nonce_format(cmd_parms *cmd, void *config, + const char *fmt) + { +- ((digest_config_rec *) config)->nonce_format = fmt; +- return "AuthDigestNonceFormat is not implemented (yet)"; ++ return "AuthDigestNonceFormat is not implemented"; + } + + static const char *set_nc_check(cmd_parms *cmd, void *config, int flag) +@@ -598,7 +590,7 @@ static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg) + { + if (!strcasecmp(alg, "MD5-sess")) { + return "AuthDigestAlgorithm: ERROR: algorithm `MD5-sess' " +- "is not fully implemented"; ++ "is not implemented"; + } + else if (strcasecmp(alg, "MD5")) { + return apr_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL); +@@ -1116,7 +1108,7 @@ static const char *gen_nonce(apr_pool_t *p, apr_time_t now, const char *opaque, + static client_entry *gen_client(const request_rec *r) + { + unsigned long op; +- client_entry new_entry = { 0, NULL, 0, "", "" }, *entry; ++ client_entry new_entry = { 0, NULL, 0, "" }, *entry; + + if (!opaque_cntr) { + return NULL; +@@ -1138,92 +1130,6 @@ static client_entry *gen_client(const request_rec *r) + + + /* +- * MD5-sess code. +- * +- * If you want to use algorithm=MD5-sess you must write get_userpw_hash() +- * yourself (see below). The dummy provided here just uses the hash from +- * the auth-file, i.e. it is only useful for testing client implementations +- * of MD5-sess . +- */ +- +-/* +- * get_userpw_hash() will be called each time a new session needs to be +- * generated and is expected to return the equivalent of +- * +- * h_urp = ap_md5(r->pool, +- * apr_pstrcat(r->pool, username, ":", ap_auth_name(r), ":", passwd)) +- * ap_md5(r->pool, +- * (unsigned char *) apr_pstrcat(r->pool, h_urp, ":", resp->nonce, ":", +- * resp->cnonce, NULL)); +- * +- * or put differently, it must return +- * +- * MD5(MD5(username ":" realm ":" password) ":" nonce ":" cnonce) +- * +- * If something goes wrong, the failure must be logged and NULL returned. +- * +- * You must implement this yourself, which will probably consist of code +- * contacting the password server with the necessary information (typically +- * the username, realm, nonce, and cnonce) and receiving the hash from it. +- * +- * TBD: This function should probably be in a seperate source file so that +- * people need not modify mod_auth_digest.c each time they install a new +- * version of apache. +- */ +-static const char *get_userpw_hash(const request_rec *r, +- const digest_header_rec *resp, +- const digest_config_rec *conf) +-{ +- return ap_md5(r->pool, +- (unsigned char *) apr_pstrcat(r->pool, conf->ha1, ":", resp->nonce, +- ":", resp->cnonce, NULL)); +-} +- +- +-/* Retrieve current session H(A1). If there is none and "generate" is +- * true then a new session for MD5-sess is generated and stored in the +- * client struct; if generate is false, or a new session could not be +- * generated then NULL is returned (in case of failure to generate the +- * failure reason will have been logged already). +- */ +-static const char *get_session_HA1(const request_rec *r, +- digest_header_rec *resp, +- const digest_config_rec *conf, +- int generate) +-{ +- const char *ha1 = NULL; +- +- /* return the current sessions if there is one */ +- if (resp->opaque && resp->client && resp->client->ha1[0]) { +- return resp->client->ha1; +- } +- else if (!generate) { +- return NULL; +- } +- +- /* generate a new session */ +- if (!resp->client) { +- resp->client = gen_client(r); +- } +- if (resp->client) { +- ha1 = get_userpw_hash(r, resp, conf); +- if (ha1) { +- memcpy(resp->client->ha1, ha1, sizeof(resp->client->ha1)); +- } +- } +- +- return ha1; +-} +- +- +-static void clear_session(const digest_header_rec *resp) +-{ +- if (resp->client) { +- resp->client->ha1[0] = '\0'; +- } +-} +- +-/* + * Authorization challenge generation code (for WWW-Authenticate) + */ + +@@ -1264,8 +1170,7 @@ static void note_digest_auth_failure(request_rec *r, + + if (resp->opaque == NULL) { + /* new client */ +- if ((conf->check_nc || conf->nonce_lifetime == 0 +- || !strcasecmp(conf->algorithm, "MD5-sess")) ++ if ((conf->check_nc || conf->nonce_lifetime == 0) + && (resp->client = gen_client(r)) != NULL) { + opaque = ltox(r->pool, resp->client->key); + } +@@ -1305,15 +1210,6 @@ static void note_digest_auth_failure(request_rec *r, + memcpy(resp->client->last_nonce, nonce, NONCE_LEN+1); + } + +- /* Setup MD5-sess stuff. Note that we just clear out the session +- * info here, since we can't generate a new session until the request +- * from the client comes in with the cnonce. +- */ +- +- if (!strcasecmp(conf->algorithm, "MD5-sess")) { +- clear_session(resp); +- } +- + /* setup domain attribute. We want to send this attribute wherever + * possible so that the client won't send the Authorization header + * unneccessarily (it's usually > 200 bytes!). +@@ -1533,24 +1429,9 @@ static const char *new_digest(const request_rec *r, + { + const char *ha1, *ha2, *a2; + +- if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess")) { +- ha1 = get_session_HA1(r, resp, conf, 1); +- if (!ha1) { +- return NULL; +- } +- } +- else { +- ha1 = conf->ha1; +- } ++ ha1 = conf->ha1; + +- if (resp->message_qop && !strcasecmp(resp->message_qop, "auth-int")) { +- a2 = apr_pstrcat(r->pool, resp->method, ":", resp->uri, ":", +- ap_md5(r->pool, (const unsigned char*) ""), NULL); +- /* TBD */ +- } +- else { +- a2 = apr_pstrcat(r->pool, resp->method, ":", resp->uri, NULL); +- } ++ a2 = apr_pstrcat(r->pool, resp->method, ":", resp->uri, NULL); + ha2 = ap_md5(r->pool, (const unsigned char *)a2); + + return ap_md5(r->pool, +@@ -1790,8 +1671,7 @@ static int authenticate_digest_user(request_rec *r) + } + + if (resp->algorithm != NULL +- && strcasecmp(resp->algorithm, "MD5") +- && strcasecmp(resp->algorithm, "MD5-sess")) { ++ && strcasecmp(resp->algorithm, "MD5")) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Digest: unknown algorithm `%s' received: %s", + resp->algorithm, r->uri); +@@ -1997,27 +1877,9 @@ static int add_auth_info(request_rec *r) + + /* calculate rspauth attribute + */ +- if (resp->algorithm && !strcasecmp(resp->algorithm, "MD5-sess")) { +- ha1 = get_session_HA1(r, resp, conf, 0); +- if (!ha1) { +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, +- "Digest: internal error: couldn't find session " +- "info for user %s", resp->username); +- return !OK; +- } +- } +- else { +- ha1 = conf->ha1; +- } ++ ha1 = conf->ha1; + +- if (resp->message_qop && !strcasecmp(resp->message_qop, "auth-int")) { +- a2 = apr_pstrcat(r->pool, ":", resp->uri, ":", +- ap_md5(r->pool,(const unsigned char *) ""), NULL); +- /* TBD */ +- } +- else { +- a2 = apr_pstrcat(r->pool, ":", resp->uri, NULL); +- } ++ a2 = apr_pstrcat(r->pool, ":", resp->uri, NULL); + ha2 = ap_md5(r->pool, (const unsigned char *)a2); + + resp_dig = ap_md5(r->pool, +-- +2.7.4 + diff -Nru apache2-2.2.22/debian/patches/0006-CVE-2019-0217.patch apache2-2.2.22/debian/patches/0006-CVE-2019-0217.patch --- apache2-2.2.22/debian/patches/0006-CVE-2019-0217.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/0006-CVE-2019-0217.patch 2019-04-09 18:45:13.000000000 +0000 @@ -0,0 +1,136 @@ +From 44b3ddc560c490c60600998fa2bf59b142d08e05 Mon Sep 17 00:00:00 2001 +From: Joe Orton +Date: Tue, 12 Mar 2019 09:24:26 +0000 +Subject: [PATCH] Merge r1853190 from trunk: + +Fix a race condition. Authentication with valid credentials could be +refused in case of concurrent accesses from different users. + +PR: 63124 +Submitted by: Simon Kappel +Reviewed by: jailletc36, icing, jorton + + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1855298 13f79535-47bb-0310-9956-ffa450edef68 +--- + +--- + modules/aaa/mod_auth_digest.c | 26 ++++++++++++-------------- + 1 file changed, 12 insertions(+), 14 deletions(-) + +diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c +index e315bd5..e902b75 100644 +--- a/modules/aaa/mod_auth_digest.c ++++ b/modules/aaa/mod_auth_digest.c +@@ -93,7 +93,6 @@ typedef struct digest_config_struct { + int check_nc; + const char *algorithm; + char *uri_list; +- const char *ha1; + } digest_config_rec; + + +@@ -154,6 +153,7 @@ typedef struct digest_header_struct { + const char *raw_request_uri; + apr_uri_t *psd_request_uri; + int needed_auth; ++ const char *ha1; + client_entry *client; + } digest_header_rec; + +@@ -1245,7 +1245,7 @@ static void note_digest_auth_failure(request_rec *r, + */ + + static authn_status get_hash(request_rec *r, const char *user, +- digest_config_rec *conf) ++ digest_config_rec *conf, const char **rethash) + { + authn_status auth_result; + char *password; +@@ -1296,7 +1296,7 @@ static authn_status get_hash(request_rec *r, const char *user, + } while (current_provider); + + if (auth_result == AUTH_USER_FOUND) { +- conf->ha1 = password; ++ *rethash = password; + } + + return auth_result; +@@ -1411,25 +1411,24 @@ static int check_nonce(request_rec *r, digest_header_rec *resp, + + /* RFC-2069 */ + static const char *old_digest(const request_rec *r, +- const digest_header_rec *resp, const char *ha1) ++ const digest_header_rec *resp) + { + const char *ha2; + + ha2 = ap_md5(r->pool, (unsigned char *)apr_pstrcat(r->pool, resp->method, ":", + resp->uri, NULL)); + return ap_md5(r->pool, +- (unsigned char *)apr_pstrcat(r->pool, ha1, ":", resp->nonce, +- ":", ha2, NULL)); ++ (unsigned char *)apr_pstrcat(r->pool, resp->ha1, ":", ++ resp->nonce, ":", ha2, NULL)); + } + + /* RFC-2617 */ + static const char *new_digest(const request_rec *r, +- digest_header_rec *resp, +- const digest_config_rec *conf) ++ digest_header_rec *resp) + { + const char *ha1, *ha2, *a2; + +- ha1 = conf->ha1; ++ ha1 = resp->ha1; + + a2 = apr_pstrcat(r->pool, resp->method, ":", resp->uri, NULL); + ha2 = ap_md5(r->pool, (const unsigned char *)a2); +@@ -1442,7 +1441,6 @@ static const char *new_digest(const request_rec *r, + NULL)); + } + +- + static void copy_uri_components(apr_uri_t *dst, + apr_uri_t *src, request_rec *r) { + if (src->scheme && src->scheme[0] != '\0') { +@@ -1679,7 +1677,7 @@ static int authenticate_digest_user(request_rec *r) + return HTTP_UNAUTHORIZED; + } + +- return_code = get_hash(r, r->user, conf); ++ return_code = get_hash(r, r->user, conf, &resp->ha1); + + if (return_code == AUTH_USER_NOT_FOUND) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, +@@ -1709,7 +1707,7 @@ static int authenticate_digest_user(request_rec *r) + + if (resp->message_qop == NULL) { + /* old (rfc-2069) style digest */ +- if (strcmp(resp->digest, old_digest(r, resp, conf->ha1))) { ++ if (strcmp(resp->digest, old_digest(r, resp))) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Digest: user %s: password mismatch: %s", r->user, + r->uri); +@@ -1737,7 +1735,7 @@ static int authenticate_digest_user(request_rec *r) + return HTTP_UNAUTHORIZED; + } + +- exp_digest = new_digest(r, resp, conf); ++ exp_digest = new_digest(r, resp); + if (!exp_digest) { + /* we failed to allocate a client struct */ + return HTTP_INTERNAL_SERVER_ERROR; +@@ -1877,7 +1875,7 @@ static int add_auth_info(request_rec *r) + + /* calculate rspauth attribute + */ +- ha1 = conf->ha1; ++ ha1 = resp->ha1; + + a2 = apr_pstrcat(r->pool, ":", resp->uri, NULL); + ha2 = ap_md5(r->pool, (const unsigned char *)a2); +-- +2.7.4 + diff -Nru apache2-2.2.22/debian/patches/CVE-2016-8743-1.patch apache2-2.2.22/debian/patches/CVE-2016-8743-1.patch --- apache2-2.2.22/debian/patches/CVE-2016-8743-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2016-8743-1.patch 2019-04-09 18:45:12.000000000 +0000 @@ -0,0 +1,38 @@ +diff --git a/server/protocol.c b/server/protocol.c +index c6d0d42..8e79082 100644 +--- a/server/protocol.c ++++ b/server/protocol.c +@@ -442,8 +442,13 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + } + } + } +- + *read = bytes_handled; ++ ++ /* PR#43039: We shouldn't accept NULL bytes within the line */ ++ if (strlen(*s) < bytes_handled - 1) { ++ return APR_EINVAL; ++ } ++ + return APR_SUCCESS; + } + +@@ -623,14 +628,15 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) + * buffer before finding the end-of-line. This is only going to + * happen if it exceeds the configured limit for a request-line. + */ +- if (rv == APR_ENOSPC) { ++ 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; + } ++ else if (APR_STATUS_IS_EINVAL(rv)) { ++ r->status = HTTP_BAD_REQUEST; ++ } + return 0; + } + } while ((len <= 0) && (++num_blank_lines < max_blank_lines)); diff -Nru apache2-2.2.22/debian/patches/CVE-2016-8743-2.patch apache2-2.2.22/debian/patches/CVE-2016-8743-2.patch --- apache2-2.2.22/debian/patches/CVE-2016-8743-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2016-8743-2.patch 2019-04-09 18:45:11.000000000 +0000 @@ -0,0 +1,23 @@ +From: rpluem +Date: Tue Dec 20 08:59:06 UTC 2016 +Subject: needed for CVE-2016-8743.patch + +Merge r892808 from trunk: + +Fix up r892678 as pointed out by rpluem. + +Origin: upstream, https://svn.apache.org/viewvc?view=revision&revision=1775232 + +diff --git a/server/protocol.c b/server/protocol.c +index 8e79082..f2ec4d1 100644 +--- a/server/protocol.c ++++ b/server/protocol.c +@@ -445,7 +445,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + *read = bytes_handled; + + /* PR#43039: We shouldn't accept NULL bytes within the line */ +- if (strlen(*s) < bytes_handled - 1) { ++ if (strlen(*s) < bytes_handled) { + return APR_EINVAL; + } + diff -Nru apache2-2.2.22/debian/patches/CVE-2016-8743-3.patch apache2-2.2.22/debian/patches/CVE-2016-8743-3.patch --- apache2-2.2.22/debian/patches/CVE-2016-8743-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2016-8743-3.patch 2019-04-09 18:45:13.000000000 +0000 @@ -0,0 +1,16 @@ +Description: restore underscore support after review from maintainer, source: +Origin: https://lists.debian.org/148730694.snktysjLZW@k + +diff --git a/server/vhost.c b/server/vhost.c +index 51b17f9..17dacd4 100644 +--- a/server/vhost.c ++++ b/server/vhost.c +@@ -757,7 +757,7 @@ static apr_status_t strict_hostname_check(request_rec *r, char *host) + int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0; + + for (ch = host; *ch; ch++) { +- if (apr_isalpha(*ch) || *ch == '-') { ++ if (apr_isalpha(*ch) || *ch == '-' || *ch == '_') { + is_dotted_decimal = 0; + } + else if (ch[0] == '.') { diff -Nru apache2-2.2.22/debian/patches/CVE-2016-8743-4.patch apache2-2.2.22/debian/patches/CVE-2016-8743-4.patch --- apache2-2.2.22/debian/patches/CVE-2016-8743-4.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2016-8743-4.patch 2019-04-09 18:45:11.000000000 +0000 @@ -0,0 +1,25 @@ +Description: fix regression introduced in CVE-2016-8743 + The messy CVE-2016-8743 patchset introduced an error in protocol + initialization in some error cases. This makes sure that invalid + requests doesn't segfault apache. + . + This is similar, but not directly related to CVE-2015-0253. +Origin: https://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/server/protocol.c?r1=1642403&r2=1668879&pathrev=1668879&view=patch +Bug-Debian: 858373 +Forwarded: not-needed +Author: Antoine Beaupré +Last-update: 2017-07-19 + +diff --git a/server/protocol.c b/server/protocol.c +index f2ec4d1..333c46a 100644 +--- a/server/protocol.c ++++ b/server/protocol.c +@@ -637,6 +637,8 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) + 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)); diff -Nru apache2-2.2.22/debian/patches/CVE-2016-8743.patch apache2-2.2.22/debian/patches/CVE-2016-8743.patch --- apache2-2.2.22/debian/patches/CVE-2016-8743.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2016-8743.patch 2019-04-09 18:45:13.000000000 +0000 @@ -0,0 +1,2145 @@ +Description: fixes for CVE-2016-8743 + those are r1777405 and r1777999 from the 2.2.23 tag of the + upstream repository: + + http://svn.apache.org/viewvc?view=revision&revision=1777405 + http://svn.apache.org/viewvc?view=revision&revision=1777999 + +diff --git a/docs/manual/mod/core.html b/docs/manual/mod/core.html +index 81c99a4..abed022 100644 +--- a/docs/manual/mod/core.html ++++ b/docs/manual/mod/core.html +@@ -19,3 +19,93 @@ Content-type: text/html; charset=UTF-8 + URI: core.html.tr.utf8 + Content-Language: tr + Content-type: text/html; charset=UTF-8 ++ ++HttpProtocolOptions ++Modify restrictions on HTTP Request Messages ++HttpProtocolOptions [Strict|Unsafe] [RegisteredMethods|LenientMethods] ++ [Allow0.9|Require1.0] ++HttpProtocolOptions Strict LenientMethods Allow0.9 ++server config ++virtual host ++2.2.32 or 2.4.24 and later ++ ++ ++

This directive changes the rules applied to the HTTP Request Line ++ (RFC 7230 §3.1.1) and the HTTP Request Header Fields ++ (RFC 7230 §3.2), which are now applied by default or using ++ the Strict option. Due to legacy modules, applications or ++ custom user-agents which must be deperecated the Unsafe ++ 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.

++ ++

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. ++ RFC 7230 §9.4 Request Splitting and ++ §9.5 Response Smuggling call out only two of the potential ++ risks of accepting non-conformant request messages, while ++ RFC 7230 §3.5 "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 Strict operating ++ mode, and the strict whitespace suggested by section 3.5 is enforced ++ and cannot be relaxed.

++ ++

Users are strongly cautioned against toggling the Unsafe ++ 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.

++ ++

Reviewing the messages logged to the ErrorLog, ++ configured with LogLevel debug 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.

++ ++

RFC 7231 §4.1 "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 ++ LenientMethods option is used, but administrators may wish ++ to toggle the RegisteredMethods option and register any ++ non-standard methods using the RegisterHttpMethod ++ directive, particularly if the Unsafe option has been toggled. ++ The RegisteredMethods option should not ++ be toggled for forward proxy hosts, as the methods supported by the ++ origin servers are unknown to the proxy server.

++ ++

RFC 2616 §19.6 "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 ++ RFC 7230 Appendix A. The Require1.0 option allows ++ the user to remove support of the default Allow0.9 option's ++ behavior.

++
++
++ ++ ++RegisterHttpMethod ++Register non-standard HTTP methods ++RegisterHttpMethod method [method [...]] ++server config ++ ++

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 RegisterHttpMethod 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.

++
++
+diff --git a/include/http_core.h b/include/http_core.h +index 1aa9bd9..f945d74 100644 +--- a/include/http_core.h ++++ b/include/http_core.h +@@ -618,6 +618,21 @@ typedef struct { + #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 --git a/include/http_protocol.h b/include/http_protocol.h +index d18f1ed..15bb224 100644 +--- a/include/http_protocol.h ++++ b/include/http_protocol.h +@@ -493,17 +493,22 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw); + */ + 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 +@@ -521,7 +526,9 @@ AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold); + * @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 +@@ -530,7 +537,7 @@ AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold); + #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) \ +@@ -540,7 +547,7 @@ AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, + /** @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 --git a/include/httpd.h b/include/httpd.h +index 675b2ab..a8655b6 100644 +--- a/include/httpd.h ++++ b/include/httpd.h +@@ -1410,6 +1410,28 @@ AP_DECLARE(char *) ap_get_list_item(apr_pool_t *p, const char **field); + */ + AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line, const char *tok); + ++/* 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); ++ + /** + * Retrieve a token, spacing over it and adjusting the pointer to + * the first non-white byte afterwards. Note that these tokens +diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c +index cc8a840..799b2b4 100644 +--- a/modules/http/http_filters.c ++++ b/modules/http/http_filters.c +@@ -108,14 +108,15 @@ static apr_status_t bail_out_on_error(http_ctx_t *ctx, + + /** + * 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; + +@@ -128,6 +129,12 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, + 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) { +@@ -135,7 +142,7 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, + } + else { + /* +- * LF expected. ++ * CRLF expected. + */ + return APR_EINVAL; + } +@@ -162,6 +169,12 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, + } + + 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; + } +@@ -183,14 +196,17 @@ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, + } + 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) { +@@ -306,7 +322,10 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + 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; +@@ -314,9 +333,6 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + 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); +@@ -497,7 +513,7 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + 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, +@@ -639,14 +655,122 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, + 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, ++ "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, ++ "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, ++ "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_table_entry_t *)headers->elts)[i]; ++ 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 = ++ (core_server_config *)ap_get_module_config(r->server->module_config, ++ &core_module); ++ ++ 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, +@@ -1118,6 +1242,7 @@ AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r) + + 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, +@@ -1133,19 +1258,23 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, + 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)) +@@ -1162,10 +1291,44 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, + 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); +@@ -1222,6 +1385,10 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, + 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 (strcasecmp(ctype, NO_CONTENT_TYPE)) { + apr_table_setn(r->headers_out, "Content-Type", ctype); +@@ -1310,11 +1477,11 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, + 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 --git a/server/core.c b/server/core.c +index 07e0997..8e75fc0 100644 +--- a/server/core.c ++++ b/server/core.c +@@ -546,6 +546,15 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) + ? virt->merge_trailers + : base->merge_trailers; + ++ 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; ++ + return conf; + } + +@@ -3241,6 +3250,57 @@ static const char *add_ct_output_filters(cmd_parms *cmd, void *conf_, + + return NULL; + } ++ ++static const char *set_http_protocol_options(cmd_parms *cmd, void *dummy, ++ const char *arg) ++{ ++ core_server_config *conf = ap_get_module_config(cmd->server->module_config, ++ &core_module); ++ 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; ++} ++ + /* + * Insert filters requested by the AddOutputFilterByType + * configuration directive. We cannot add filters based +@@ -3550,6 +3610,12 @@ AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF, + #endif + 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 --git a/server/gen_test_char.c b/server/gen_test_char.c +index a0b5510..01f1ac5 100644 +--- a/server/gen_test_char.c ++++ b/server/gen_test_char.c +@@ -16,11 +16,11 @@ + + #ifdef CROSS_COMPILE + ++#include + #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 + #define APR_HAVE_STDIO_H 1 + #define APR_HAVE_STRING_H 1 + +@@ -51,11 +51,13 @@ + #define T_HTTP_TOKEN_STOP (0x08) + #define T_ESCAPE_LOGITEM (0x10) + #define T_ESCAPE_FORENSIC (0x20) ++#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" +@@ -65,18 +67,22 @@ int main(int argc, char *argv[]) + "#define T_HTTP_TOKEN_STOP (%u)\n" + "#define T_ESCAPE_LOGITEM (%u)\n" + "#define T_ESCAPE_FORENSIC (%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_FORENSIC, ++ 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 */ +@@ -104,15 +110,36 @@ int main(int argc, char *argv[]) + flags |= T_ESCAPE_PATH_SEGMENT; + } + +- if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=/~", c)) { ++ if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:;@&=/~", c)) { + flags |= T_OS_ESCAPE_PATH; + } + +- /* 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) +@@ -130,7 +157,7 @@ int main(int argc, char *argv[]) + flags |= T_ESCAPE_FORENSIC; + } + +- printf("%u%c", flags, (c < 255) ? ',' : ' '); ++ printf("0x%03x%c", flags, (c < 255) ? ',' : ' '); + } + + printf("\n};\n"); +diff --git a/server/protocol.c b/server/protocol.c +index 71cbcf7..c6d0d42 100644 +--- a/server/protocol.c ++++ b/server/protocol.c +@@ -183,12 +183,13 @@ AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime) + return (mtime > now) ? now : mtime; + } + +-/* Min # of bytes to allocate when reading a request line */ +-#define MIN_LINE_ALLOC 80 +- + /* Get a line of protocol input, including any continuation lines + * 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. + * +@@ -198,7 +199,7 @@ AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime) + * 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 +@@ -209,32 +210,35 @@ AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime) + */ + 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 + * against APR_ASCII_LF at the end of the loop if bb only contains + * zero-length buckets. + */ +- if (last_char) { ++ if (last_char) + *last_char = '\0'; +- } + + 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; + } +@@ -285,9 +289,6 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + /* We'll assume the common case where one bucket is enough. */ + if (!*s) { + current_alloc = len; +- if (current_alloc < MIN_LINE_ALLOC) { +- current_alloc = MIN_LINE_ALLOC; +- } + *s = apr_palloc(r->pool, current_alloc); + } + else if (bytes_handled + len > current_alloc) { +@@ -323,6 +324,13 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + } + } + ++ 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) { +@@ -345,7 +353,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + 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; +@@ -396,7 +404,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + */ + if (do_alloc) { + tmp = NULL; +- } else { ++ } ++ else { + /* We're null terminated. */ + tmp = last_char; + } +@@ -461,7 +470,7 @@ AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, + } + #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 +478,7 @@ AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold) + 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. */ +@@ -552,25 +561,36 @@ AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri) + } + } + +-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; ++} + +-#if 0 +- conn_rec *conn = r->connection; +-#endif +- 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; ++ core_server_config *conf = ++ (core_server_config *)ap_get_module_config(r->server->module_config, ++ &core_module); ++ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + int num_blank_lines = 0; + int max_blank_lines = r->server->limit_req_fields; + + if (max_blank_lines <= 0) { + max_blank_lines = DEFAULT_LIMIT_REQUEST_FIELDS; + } +- + /* Read past empty lines until we get a real request line, + * a read error, the connection closes (EOF), or we timeout. + * +@@ -594,7 +614,7 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) + */ + 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(); +@@ -618,21 +638,155 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) + /* we've probably got something to do, ignore graceful restart requests */ + + r->request_time = apr_time_now(); +- ll = r->the_request; +- r->method = ap_getword_white(r->pool, &ll); +- +-#if 0 +-/* XXX If we want to keep track of the Method, the protocol module should do +- * it. That support isn't in the scoreboard yet. Hopefully next week +- * sometime. rbb */ +- ap_update_connection_status(AP_CHILD_THREAD_FROM_ID(conn->id), "Method", +- r->method); +-#endif ++ 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) ++ ; ++ } ++ ++ /* 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); ++ } ++ ++ if (((ll == r->method) || (*ll && !apr_isspace(*ll))) ++ && deferred_error == rrl_none) { ++ deferred_error = rrl_badmethod; ++ ll = strpbrk(ll, "\t\n\v\f\r "); ++ } ++ ++ /* 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; ++ } ++ ++ /* 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 "); ++ } ++ ++ /* 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'; + +- uri = ap_getword_white(r->pool, &ll); ++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); + +- /* Provide quick information about the request method as soon as known */ ++ 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; ++ 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; ++ r->protocol = apr_pstrdup(r->pool, "HTTP/0.9"); ++ r->proto_num = HTTP_VERSION(0, 9); ++ } + ++ /* 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; +@@ -640,44 +794,123 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb) + + ap_parse_uri(r, uri); + +- if (ll[0]) { +- r->assbackwards = 0; +- pro = ll; +- len = strlen(ll); +- } else { +- r->assbackwards = 1; +- pro = "HTTP/0.9"; +- len = 8; ++ /* 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; + } +- r->protocol = apr_pstrmemdup(r->pool, pro, len); + +- /* XXX ap_update_connection_status(conn->id, "Protocol", r->protocol); */ ++ /* 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, ++ "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, ++ "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, ++ "HTTP Request Line; Missing URI"); ++ else if (deferred_error == rrl_baduri) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, ++ "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, ++ "HTTP Request Line; Invalid whitespace"); ++ else if (deferred_error == rrl_excesswhitespace) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, ++ "HTTP Request Line; Excess whitespace " ++ "(disallowed by HttpProtocolOptions Strict"); ++ else if (deferred_error == rrl_trailingtext) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, ++ "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, ++ "HTTP Request Line; Rejected HTTP/0.9 request"); ++ else if (deferred_error == rrl_badprotocol) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, ++ "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; ++ } + +- /* 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); ++ if (conf->http_methods == AP_HTTP_METHODS_REGISTERED ++ && r->method_number == M_INVALID) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, ++ "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, ++ "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, ++ "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, ++ "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; + } + +-/* 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) ++static int table_do_fn_check_lengths(void *r_, const char *key, ++ const char *value) + { +- const char *end = ap_strchr_c(field, ':'); +- if (end == NULL || end - field > LOG_NAME_MAX_LEN) +- return LOG_NAME_MAX_LEN; +- return end - field; ++ request_rec *r = r_; ++ if (value == NULL || r->server->limit_req_fieldsize >= strlen(value) ) ++ return 1; ++ ++ r->status = HTTP_BAD_REQUEST; ++ 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, "Request " ++ "header exceeds LimitRequestFieldSize after merging: %.*s", ++ field_name_len(key), key); ++ return 0; + } + + AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb) +@@ -690,6 +923,10 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb + apr_size_t len; + int fields_read = 0; + char *tmp_field; ++ core_server_config *conf = ++ (core_server_config *)ap_get_module_config(r->server->module_config, ++ &core_module); ++ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + + /* + * Read header lines until we get the empty separator line, a read error, +@@ -697,11 +934,10 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb + */ + 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)) { +@@ -717,145 +953,224 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb + * finding the end-of-line. This is only going to happen if it + * exceeds the configured limit for a field size. + */ +- if (rv == APR_ENOSPC && field) { +- /* insure ap_escape_html will terminate correctly */ +- field[len - 1] = '\0'; ++ if (rv == APR_ENOSPC) { + apr_table_setn(r->notes, "error-notes", +- apr_psprintf(r->pool, +- "Size of a request header field " +- "exceeds server limit.
\n" +- "
\n%.*s\n
/n", +- field_name_len(field), +- ap_escape_html(r->pool, field))); +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, +- "Request header exceeds LimitRequestFieldSize: " +- "%.*s", field_name_len(field), field); ++ "Size of a request header field " ++ "exceeds server limit."); ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, ++ "Request header exceeds LimitRequestFieldSize%s" ++ "%.*s", ++ (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 */ ++ /* 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 (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.
\n" +- "
\n%.*s\n
\n", +- field_name_len(last_field), +- ap_escape_html(r->pool, last_field))); +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, +- "Request header exceeds LimitRequestFieldSize " +- "after folding: %.*s", +- field_name_len(last_field), last_field); +- return; +- } ++ 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, ++ "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, ++ "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, ++ "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; ++ fold_buf = (char *)apr_palloc(r->pool, alloc_len); ++ memcpy(fold_buf, last_field, last_len); ++ last_field = fold_buf; + } +- else /* not a continuation line */ { ++ 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; ++ /* We've appended this obs-fold line to last_len, proceed to ++ * read the next input line ++ */ ++ continue; ++ } ++ else if (last_field != NULL) { + +- if (r->server->limit_req_fields ++ /* 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."); +- 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, ++ "Number of request headers exceeds " ++ "LimitRequestFields"); ++ return; ++ } ++ ++ 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 */ +- apr_table_setn(r->notes, "error-notes", +- apr_psprintf(r->pool, +- "Request header field is " +- "missing ':' separator.
\n" +- "
\n%.*s
\n", +- (int)LOG_NAME_MAX_LEN, +- ap_escape_html(r->pool, +- last_field))); ++ if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ ++ r->status = HTTP_BAD_REQUEST; /* abort bad request */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "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, ++ "Request header field name presented" ++ " invalid whitespace"); ++ return; ++ } ++ + while (*value == ' ' || *value == '\t') { + ++value; /* Skip to start of value */ + } + +- /* Strip LWS after field-name: */ +- while (tmp_field > last_field +- && (*tmp_field == ' ' || *tmp_field == '\t')) { +- *tmp_field-- = '\0'; ++ if (strpbrk(value, "\n\v\f\r")) { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, ++ "Request header field value presented" ++ " bad whitespace"); ++ 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'; ++ if (tmp_field == last_field) { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, ++ "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, ++ "Request header field name is malformed: " ++ "%.*s", (int)LOG_NAME_MAX_LEN, last_field); ++ return; ++ } ++ ++ *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, ++ "Request header value is malformed: " ++ "%.*s", (int)LOG_NAME_MAX_LEN, value); ++ return; ++ } ++ } + +- } /* end if current line is not a continuation starting with tab */ +- } ++ apr_table_addn(r->headers_in, last_field, value); ++ ++ /* 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; + } + + apr_table_compress(r->headers_in, APR_OVERLAP_TABLES_MERGE); ++ ++ /* enforce LimitRequestFieldSize for merged headers */ ++ apr_table_do(table_do_fn_check_lengths, r, r->headers_in, NULL); + } + + AP_DECLARE(void) ap_get_mime_headers(request_rec *r) +@@ -923,26 +1238,39 @@ request_rec *ap_read_request(conn_rec *conn) + + /* Get the request... */ + if (!read_request_line(r, tmp_bb)) { +- if (r->status == HTTP_REQUEST_URI_TOO_LARGE) { +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, +- "request failed: URI too long (longer than %d)", r->server->limit_req_line); +- ap_send_error_response(r, 0); ++ 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, ++ "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_DEBUG, 0, r, ++ "request failed: malformed request line"); ++ } ++ 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); + return r; +- } +- else if (r->status == HTTP_REQUEST_TIME_OUT) { ++ case HTTP_REQUEST_TIME_OUT: + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); +- if (!r->connection->keepalives) { ++ if (!r->connection->keepalives) + ap_run_log_transaction(r); +- } + apr_brigade_destroy(tmp_bb); + return r; ++ default: ++ apr_brigade_destroy(tmp_bb); ++ r = NULL; ++ return r; + } +- +- apr_brigade_destroy(tmp_bb); +- return NULL; + } + + /* We may have been in keep_alive_timeout mode, so toggle back +@@ -959,7 +1287,7 @@ request_rec *ap_read_request(conn_rec *conn) + if (!r->assbackwards) { + ap_get_mime_headers_core(r, tmp_bb); + if (r->status != HTTP_OK) { +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "request failed: error reading the headers"); + ap_send_error_response(r, 0); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); +@@ -977,25 +1305,6 @@ request_rec *ap_read_request(conn_rec *conn) + 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_ERR, 0, r, +- "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); +- return r; +- } +- } + + apr_brigade_destroy(tmp_bb); + +@@ -1003,6 +1312,7 @@ request_rec *ap_read_request(conn_rec *conn) + * now read. may update status. + */ + ap_update_vhost_from_headers(r); ++ access_status = r->status; + + /* Toggle to the Host:-based vhost's timeout mode to fetch the + * request body and send the response body, if needed. +@@ -1025,8 +1335,8 @@ request_rec *ap_read_request(conn_rec *conn) + * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain + * a Host: header, and the server MUST respond with 400 if it doesn't. + */ +- r->status = HTTP_BAD_REQUEST; +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, ++ access_status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "client sent HTTP/1.1 request without hostname " + "(see RFC2616 section 14.23): %s", r->uri); + } +@@ -1041,14 +1351,8 @@ request_rec *ap_read_request(conn_rec *conn) + ap_add_input_filter_handle(ap_http_input_filter_handle, + NULL, r, r->connection); + +- if (r->status != HTTP_OK) { +- ap_send_error_response(r, 0); +- ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); +- ap_run_log_transaction(r); +- return r; +- } +- +- if ((access_status = ap_run_post_read_request(r))) { ++ if (access_status != HTTP_OK ++ || (access_status = ap_run_post_read_request(r))) { + ap_die(access_status, r); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); +@@ -1248,7 +1552,7 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) + + if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) { + /* Client tried to authenticate using wrong auth scheme */ +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + "client used wrong authentication scheme: %s", r->uri); + ap_note_basic_auth_failure(r); + return HTTP_UNAUTHORIZED; +diff --git a/server/util.c b/server/util.c +index a50d034..98c46f9 100644 +--- a/server/util.c ++++ b/server/util.c +@@ -71,7 +71,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. +@@ -1390,6 +1390,36 @@ AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line, + return good; + } + ++/* 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 +diff --git a/server/vhost.c b/server/vhost.c +index b8e9ca7..51b17f9 100644 +--- a/server/vhost.c ++++ b/server/vhost.c +@@ -687,6 +687,113 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) + * 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_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, ++ "[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. + * +@@ -700,78 +807,90 @@ AP_DECLARE(void) ap_fini_vhost_config(apr_pool_t *p, server_rec *main_s) + * 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, ++ "[strict] purely numeric host names not allowed: %s", ++ src); ++ goto bad_nolog; ++ } ++ r->hostname = src; ++ return is_v6literal; + } + +- rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool); +- if (rv != APR_SUCCESS || scope_id) { +- goto bad; ++ 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; + } +- +- 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 ++ else { ++ /* ++ * Already parsed, surrounding [ ] (if IPv6 literal) and :port have ++ * already been removed. + */ +- r->parsed_uri.port = port; +- r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); ++ host = apr_pstrdup(r->pool, r->hostname); ++ if (ap_strchr(host, ':') != NULL) ++ is_v6literal = 1; + } + +- /* 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'; +- } ++ if (is_v6literal) { ++ rv = fix_hostname_v6_literal(r, host); ++ } ++ 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; ++ + r->hostname = host; +- return; ++ return is_v6literal; + + bad: ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, ++ "Client sent malformed Host header: %s", ++ src); ++bad_nolog: + r->status = HTTP_BAD_REQUEST; +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, +- "Client sent malformed Host header"); +- return; ++ return is_v6literal; + } + +- + /* return 1 if host matches ServerName or ServerAliases */ + static int matches_aliases(server_rec *s, const char *host) + { +@@ -968,15 +1087,78 @@ static void check_serverpath(request_rec *r) + } + } + ++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 = ++ (core_server_config *)ap_get_module_config(r->server->module_config, ++ &core_module); ++ 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, ++ "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) diff -Nru apache2-2.2.22/debian/patches/CVE-2017-3167.patch apache2-2.2.22/debian/patches/CVE-2017-3167.patch --- apache2-2.2.22/debian/patches/CVE-2017-3167.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2017-3167.patch 2019-04-09 18:45:12.000000000 +0000 @@ -0,0 +1,158 @@ +------------------------------------------------------------------------ +r1799232 | covener | 2017-06-19 13:01:50 -0400 (Mon, 19 Jun 2017) | 13 lines + +Merge https://svn.apache.org/r1796348 from trunk: + + *) SECURITY: CVE-2017-3167 (cve.mitre.org) + Use of the ap_get_basic_auth_pw() by third-party modules outside of the + authentication phase may lead to authentication requirements being + bypassed. + [Emmanuel Dreyfus , Jacob Champion, Eric Covener] + + +Submitted By: Emmanuel Dreyfus , Jacob Champion, Eric Covener +Reviewed By: covener, ylavic, wrowe + + +------------------------------------------------------------------------ +diff --git a/include/ap_mmn.h b/include/ap_mmn.h +index bee9853..a3102ce 100644 +--- a/include/ap_mmn.h ++++ b/include/ap_mmn.h +@@ -148,6 +148,8 @@ + * altered in 2.2.18. Add ap_unescape_url_keep2f_ex(). + * 20051115.29 (2.2.21) add max_ranges to core_dir_config + * 20051115.30 (2.2.21) add ap_set_accept_ranges() ++ * 20051115.43 (2.2.33) Add ap_get_basic_auth_components() and deprecate ++ * ap_get_basic_auth_pw() + */ + + #define MODULE_MAGIC_COOKIE 0x41503232UL /* "AP22" */ +diff --git a/include/http_protocol.h b/include/http_protocol.h +index 15bb224..df4661a 100644 +--- a/include/http_protocol.h ++++ b/include/http_protocol.h +@@ -469,7 +469,11 @@ AP_DECLARE(void) ap_note_basic_auth_failure(request_rec *r); + AP_DECLARE(void) ap_note_digest_auth_failure(request_rec *r); + + /** +- * Get the password from the request headers ++ * Get the password from the request headers. This function has multiple side ++ * effects due to its prior use in the old authentication framework. ++ * ap_get_basic_auth_components() should be preferred. ++ * ++ * @deprecated @see ap_get_basic_auth_components + * @param r The current request + * @param pw The password as set in the headers + * @return 0 (OK) if it set the 'pw' argument (and assured +@@ -482,6 +486,25 @@ AP_DECLARE(void) ap_note_digest_auth_failure(request_rec *r); + */ + AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw); + ++#define AP_GET_BASIC_AUTH_PW_NOTE "AP_GET_BASIC_AUTH_PW_NOTE" ++ ++/** ++ * Get the username and/or password from the request's Basic authentication ++ * headers. Unlike ap_get_basic_auth_pw(), calling this function has no side ++ * effects on the passed request_rec. ++ * ++ * @param r The current request ++ * @param username If not NULL, set to the username sent by the client ++ * @param password If not NULL, set to the password sent by the client ++ * @return APR_SUCCESS if the credentials were successfully parsed and returned; ++ * APR_EINVAL if there was no authentication header sent or if the ++ * client was not using the Basic authentication scheme. username and ++ * password are unchanged on failure. ++ */ ++AP_DECLARE(apr_status_t) ap_get_basic_auth_components(const request_rec *r, ++ const char **username, ++ const char **password); ++ + /** + * parse_uri: break apart the uri + * @warning Side Effects: +diff --git a/server/protocol.c b/server/protocol.c +index 333c46a..fc5ad77 100644 +--- a/server/protocol.c ++++ b/server/protocol.c +@@ -1572,6 +1572,7 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) + + t = ap_pbase64decode(r->pool, auth_line); + r->user = ap_getword_nulls (r->pool, &t, ':'); ++ apr_table_setn(r->notes, AP_GET_BASIC_AUTH_PW_NOTE, "1"); + r->ap_auth_type = "Basic"; + + *pw = t; +@@ -1579,6 +1580,53 @@ AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw) + return OK; + } + ++AP_DECLARE(apr_status_t) ap_get_basic_auth_components(const request_rec *r, ++ const char **username, ++ const char **password) ++{ ++ const char *auth_header; ++ const char *credentials; ++ const char *decoded; ++ const char *user; ++ ++ auth_header = (PROXYREQ_PROXY == r->proxyreq) ? "Proxy-Authorization" ++ : "Authorization"; ++ credentials = apr_table_get(r->headers_in, auth_header); ++ ++ if (!credentials) { ++ /* No auth header. */ ++ return APR_EINVAL; ++ } ++ ++ if (strcasecmp(ap_getword(r->pool, &credentials, ' '), "Basic")) { ++ /* These aren't Basic credentials. */ ++ return APR_EINVAL; ++ } ++ ++ while (*credentials == ' ' || *credentials == '\t') { ++ credentials++; ++ } ++ ++ /* XXX Our base64 decoding functions don't actually error out if the string ++ * we give it isn't base64; they'll just silently stop and hand us whatever ++ * they've parsed up to that point. ++ * ++ * Since this function is supposed to be a drop-in replacement for the ++ * deprecated ap_get_basic_auth_pw(), don't fix this for 2.4.x. ++ */ ++ decoded = ap_pbase64decode(r->pool, credentials); ++ user = ap_getword_nulls(r->pool, &decoded, ':'); ++ ++ if (username) { ++ *username = user; ++ } ++ if (password) { ++ *password = decoded; ++ } ++ ++ return APR_SUCCESS; ++} ++ + struct content_length_ctx { + int data_sent; /* true if the C-L filter has already sent at + * least one bucket on to the next output filter +diff --git a/server/request.c b/server/request.c +index f076e63..997a809 100644 +--- a/server/request.c ++++ b/server/request.c +@@ -179,6 +179,14 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) + r->ap_auth_type = r->prev->ap_auth_type; + } + else { ++ /* A module using a confusing API (ap_get_basic_auth_pw) caused ++ ** r->user to be filled out prior to check_authn hook. We treat ++ ** it is inadvertent. ++ */ ++ if (r->user && apr_table_get(r->notes, AP_GET_BASIC_AUTH_PW_NOTE)) { ++ r->user = NULL; ++ } ++ + switch (ap_satisfies(r)) { + case SATISFY_ALL: + case SATISFY_NOSPEC: diff -Nru apache2-2.2.22/debian/patches/CVE-2017-3169.patch apache2-2.2.22/debian/patches/CVE-2017-3169.patch --- apache2-2.2.22/debian/patches/CVE-2017-3169.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2017-3169.patch 2019-04-09 18:45:12.000000000 +0000 @@ -0,0 +1,80 @@ +------------------------------------------------------------------------ +r1799229 | covener | 2017-06-19 12:52:00 -0400 (Mon, 19 Jun 2017) | 12 lines + +Merge https://svn.apache.org/r1796343 from trunk: + + *) SECURITY: CVE-2017-3169 (cve.mitre.org) + mod_ssl may dereference a NULL pointer when third-party modules call + ap_hook_process_connection() during an HTTP request to an HTTPS port. + [Yann Ylavic] + + +Submitted By: ylavic +Reviewed By: covener, ylavic, wrowe + + +------------------------------------------------------------------------ +diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c +index e2d3390..4216e1a 100644 +--- a/modules/ssl/ssl_engine_io.c ++++ b/modules/ssl/ssl_engine_io.c +@@ -865,19 +865,20 @@ static apr_status_t ssl_filter_write(ap_filter_t *f, + sizeof(HTTP_ON_HTTPS_PORT) - 1, \ + alloc) + +-static void ssl_io_filter_disable(SSLConnRec *sslconn, ap_filter_t *f) ++static void ssl_io_filter_disable(SSLConnRec *sslconn, ++ bio_filter_in_ctx_t *inctx) + { +- bio_filter_in_ctx_t *inctx = f->ctx; + SSL_free(inctx->ssl); + sslconn->ssl = NULL; + inctx->ssl = NULL; + inctx->filter_ctx->pssl = NULL; + } + +-static apr_status_t ssl_io_filter_error(ap_filter_t *f, ++static apr_status_t ssl_io_filter_error(bio_filter_in_ctx_t *inctx, + apr_bucket_brigade *bb, + apr_status_t status) + { ++ ap_filter_t *f = inctx->f; + SSLConnRec *sslconn = myConnConfig(f->c); + apr_bucket *bucket; + +@@ -890,7 +891,7 @@ static apr_status_t ssl_io_filter_error(ap_filter_t *f, + ssl_log_ssl_error(APLOG_MARK, APLOG_INFO, sslconn->server); + + sslconn->non_ssl_request = 1; +- ssl_io_filter_disable(sslconn, f); ++ ssl_io_filter_disable(sslconn, inctx); + + /* fake the request line */ + bucket = HTTP_ON_HTTPS_PORT_BUCKET(f->c->bucket_alloc); +@@ -1363,7 +1364,7 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f, + * rather than have SSLEngine On configured. + */ + if ((status = ssl_io_filter_connect(inctx->filter_ctx)) != APR_SUCCESS) { +- return ssl_io_filter_error(f, bb, status); ++ return ssl_io_filter_error(inctx, bb, status); + } + + if (is_init) { +@@ -1399,7 +1400,7 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f, + + /* Handle custom errors. */ + if (status != APR_SUCCESS) { +- return ssl_io_filter_error(f, bb, status); ++ return ssl_io_filter_error(inctx, bb, status); + } + + /* Create a transient bucket out of the decrypted data. */ +@@ -1442,7 +1443,7 @@ static apr_status_t ssl_io_filter_output(ap_filter_t *f, + inctx->block = APR_BLOCK_READ; + + if ((status = ssl_io_filter_connect(filter_ctx)) != APR_SUCCESS) { +- return ssl_io_filter_error(f, bb, status); ++ return ssl_io_filter_error(inctx, bb, status); + } + + while (!APR_BRIGADE_EMPTY(bb)) { diff -Nru apache2-2.2.22/debian/patches/CVE-2017-7668.patch apache2-2.2.22/debian/patches/CVE-2017-7668.patch --- apache2-2.2.22/debian/patches/CVE-2017-7668.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2017-7668.patch 2019-04-09 18:45:13.000000000 +0000 @@ -0,0 +1,29 @@ +------------------------------------------------------------------------ +r1799228 | covener | 2017-06-19 12:48:42 -0400 (Mon, 19 Jun 2017) | 8 lines + +Merge r1796350 from trunk: + +short-circuit on NULL + +Submitted By: jchampion +Reviewed By: jchampion, wrowe, ylavic + + +------------------------------------------------------------------------ +diff --git a/server/util.c b/server/util.c +index 98c46f9..fcce577 100644 +--- a/server/util.c ++++ b/server/util.c +@@ -1478,10 +1478,8 @@ AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok) + + s = (const unsigned char *)line; + for (;;) { +- /* find start of token, skip all stop characters, note NUL +- * isn't a token stop, so we don't need to test for it +- */ +- while (TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) { ++ /* find start of token, skip all stop characters */ ++ while (*s && TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) { + ++s; + } + if (!*s) { diff -Nru apache2-2.2.22/debian/patches/CVE-2017-7679.patch apache2-2.2.22/debian/patches/CVE-2017-7679.patch --- apache2-2.2.22/debian/patches/CVE-2017-7679.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2017-7679.patch 2019-04-09 18:45:13.000000000 +0000 @@ -0,0 +1,67 @@ +From 398f3ddeb1ceb8ba710eadf7036a36a41e0e769a Mon Sep 17 00:00:00 2001 +From: Eric Covener +Date: Mon, 5 Jun 2017 12:12:31 +0000 +Subject: [PATCH] Merge 1797550 from trunk: + +mod_mime: fix quoted pair scanning + + +Submitted By: ylavic +Reviewed By: covener, ylavic, jim + + + + + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1797653 13f79535-47bb-0310-9956-ffa450edef68 +--- + CHANGES | 2 ++ + STATUS | 5 ----- + modules/http/mod_mime.c | 4 ++-- + 3 files changed, 4 insertions(+), 7 deletions(-) + +#diff --git a/CHANGES b/CHANGES +#index 301cb7061c9..9583c92d076 100644 +#--- a/CHANGES +#+++ b/CHANGES +#@@ -2,6 +2,8 @@ +# +# Changes with Apache 2.4.26 +# +#+ *) mod_mime: Fix error checking for quoted pairs. [Yann Ylavic] +#+ +# *) mod_proxy_wstunnel: Add "upgrade" parameter to allow upgrade to other +# protocols. [Jean-Frederic Clere] +# +#diff --git a/STATUS b/STATUS +#index 191de520104..f38da4ca99b 100644 +#--- a/STATUS +#+++ b/STATUS +#@@ -120,11 +120,6 @@ RELEASE SHOWSTOPPERS: +# PATCHES ACCEPTED TO BACKPORT FROM TRUNK: +# [ start all new proposals below, under PATCHES PROPOSED. ] +# +#- *) mod_mime: Fix scanning of quoted-pairs. +#- trunk patch: http://svn.apache.org/r1797550 +#- 2.4.x patch: svn merge -c 1797550 ^/httpd/httpd/trunk . +#- +1: covener, ylavic, jim +#- +# PATCHES PROPOSED TO BACKPORT FROM TRUNK: +# [ New proposals should be added at the end of the list ] +# +diff --git a/modules/http/mod_mime.c b/modules/http/mod_mime.c +index f92119b633e..28c53be132b 100644 +--- a/modules/http/mod_mime.c ++++ b/modules/http/mod_mime.c +@@ -528,9 +528,9 @@ static int is_quoted_pair(const char *s) + int res = -1; + int c; + +- if (((s + 1) != NULL) && (*s == '\\')) { ++ if (*s == '\\') { + c = (int) *(s + 1); +- if (apr_isascii(c)) { ++ if (c && apr_isascii(c)) { + res = 1; + } + } diff -Nru apache2-2.2.22/debian/patches/CVE-2017-9788.patch apache2-2.2.22/debian/patches/CVE-2017-9788.patch --- apache2-2.2.22/debian/patches/CVE-2017-9788.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2017-9788.patch 2019-04-09 18:45:10.000000000 +0000 @@ -0,0 +1,37 @@ +From 549ba6a39aa0df78a610025f74f3a06503a70f67 Mon Sep 17 00:00:00 2001 +From: "William A. Rowe Jr" +Date: Thu, 6 Jul 2017 00:02:54 +0000 +Subject: [PATCH] Correct string scope to prevent duplicated values for + subsequent tokens. + +Submitted by: wrowe +Backports: r1800919 +Reviewed by: wrowe, jim, jchampion + +--- apache2-2.2.22.orig/modules/aaa/mod_auth_digest.c ++++ apache2-2.2.22/modules/aaa/mod_auth_digest.c +@@ -894,13 +894,13 @@ static int get_digest_rec(request_rec *r + + /* find value */ + ++ vv = 0; + if (auth_line[0] == '=') { + auth_line++; + while (apr_isspace(auth_line[0])) { + auth_line++; + } + +- vv = 0; + if (auth_line[0] == '\"') { /* quoted string */ + auth_line++; + while (auth_line[0] != '\"' && auth_line[0] != '\0') { +@@ -919,8 +919,8 @@ static int get_digest_rec(request_rec *r + value[vv++] = *auth_line++; + } + } +- value[vv] = '\0'; + } ++ value[vv] = '\0'; + + while (auth_line[0] != ',' && auth_line[0] != '\0') { + auth_line++; diff -Nru apache2-2.2.22/debian/patches/CVE-2017-9798.patch apache2-2.2.22/debian/patches/CVE-2017-9798.patch --- apache2-2.2.22/debian/patches/CVE-2017-9798.patch 1970-01-01 00:00:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/CVE-2017-9798.patch 2019-04-09 18:45:13.000000000 +0000 @@ -0,0 +1,21 @@ +Description: fix optionsbleed information leak +Origin: upstream, https://svn.apache.org/viewvc?view=revision&revision=1807754 +Bug-Debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=876109 + +diff --git a/server/core.c b/server/core.c +index 8e75fc0..b8270d5 100644 +--- a/server/core.c ++++ b/server/core.c +@@ -1805,6 +1805,12 @@ AP_CORE_DECLARE_NONSTD(const char *) ap_limit_section(cmd_parms *cmd, + /* method has not been registered yet, but resorce restriction + * is always checked before method handling, so register it. + */ ++ if (cmd->pool == cmd->temp_pool) { ++ /* In .htaccess, we can't globally register new methods. */ ++ return apr_psprintf(cmd->pool, "Could not register method '%s' " ++ "for %s from .htaccess configuration", ++ method, cmd->cmd->name); ++ } + methnum = ap_method_register(cmd->pool, method); + } + diff -Nru apache2-2.2.22/debian/patches/series apache2-2.2.22/debian/patches/series --- apache2-2.2.22/debian/patches/series 2016-07-14 12:48:00.000000000 +0000 +++ apache2-2.2.22/debian/patches/series 2019-04-09 18:45:10.000000000 +0000 @@ -46,3 +46,20 @@ ephemeral_key_handling.patch CVE-2015-3183.patch CVE-2016-5387.patch +CVE-2016-8743.patch +CVE-2016-8743-1.patch +CVE-2016-8743-2.patch +CVE-2016-8743-3.patch +CVE-2016-8743-4.patch +CVE-2017-3167.patch +CVE-2017-3169.patch +CVE-2017-7668.patch +CVE-2017-7679.patch +CVE-2017-9788.patch +CVE-2017-9798.patch +0001-CVE-2017-15710.patch +0002-CVE-2018-1301.patch +0003-CVE-2018-1312-pre.patch +0004-CVE-2018-1312-1.patch +0005-CVE-2018-1312-2.patch +0006-CVE-2019-0217.patch