diff --git a/.gitignore b/.gitignore index 260a2d6..9969f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ SOURCES/httpd-2.4.6.tar.bz2 -SOURCES/centos-noindex.tar.gz diff --git a/.httpd.metadata b/.httpd.metadata index 17ede1b..d335a99 100644 --- a/.httpd.metadata +++ b/.httpd.metadata @@ -1,2 +1 @@ 16d8ec72535ded65d035122b0d944b0e64eaa2a2 SOURCES/httpd-2.4.6.tar.bz2 -6ce5ab3c765b9efeceb2e636e32373bc6e6ed489 SOURCES/centos-noindex.tar.gz diff --git a/SOURCES/httpd-2.4.6-CVE-2016-0736.patch b/SOURCES/httpd-2.4.6-CVE-2016-0736.patch new file mode 100644 index 0000000..cb80fa0 --- /dev/null +++ b/SOURCES/httpd-2.4.6-CVE-2016-0736.patch @@ -0,0 +1,325 @@ +diff --git a/modules/session/mod_session_crypto.c b/modules/session/mod_session_crypto.c +index 4d65bb8..9231a5e 100644 +--- a/modules/session/mod_session_crypto.c ++++ b/modules/session/mod_session_crypto.c +@@ -18,6 +18,7 @@ + #include "apu_version.h" + #include "apr_base64.h" /* for apr_base64_decode et al */ + #include "apr_lib.h" ++#include "apr_md5.h" + #include "apr_strings.h" + #include "http_log.h" + #include "http_core.h" +@@ -57,6 +58,146 @@ typedef struct { + int library_set; + } session_crypto_conf; + ++/* Wrappers around apr_siphash24() and apr_crypto_equals(), ++ * available in APU-1.6/APR-2.0 only. ++ */ ++#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 6) ++ ++#include "apr_siphash.h" ++ ++#define AP_SIPHASH_DSIZE APR_SIPHASH_DSIZE ++#define AP_SIPHASH_KSIZE APR_SIPHASH_KSIZE ++#define ap_siphash24_auth apr_siphash24_auth ++ ++#define ap_crypto_equals apr_crypto_equals ++ ++#else ++ ++#define AP_SIPHASH_DSIZE 8 ++#define AP_SIPHASH_KSIZE 16 ++ ++#define ROTL64(x, n) (((x) << (n)) | ((x) >> (64 - (n)))) ++ ++#define U8TO64_LE(p) \ ++ (((apr_uint64_t)((p)[0]) ) | \ ++ ((apr_uint64_t)((p)[1]) << 8) | \ ++ ((apr_uint64_t)((p)[2]) << 16) | \ ++ ((apr_uint64_t)((p)[3]) << 24) | \ ++ ((apr_uint64_t)((p)[4]) << 32) | \ ++ ((apr_uint64_t)((p)[5]) << 40) | \ ++ ((apr_uint64_t)((p)[6]) << 48) | \ ++ ((apr_uint64_t)((p)[7]) << 56)) ++ ++#define U64TO8_LE(p, v) \ ++do { \ ++ (p)[0] = (unsigned char)((v) ); \ ++ (p)[1] = (unsigned char)((v) >> 8); \ ++ (p)[2] = (unsigned char)((v) >> 16); \ ++ (p)[3] = (unsigned char)((v) >> 24); \ ++ (p)[4] = (unsigned char)((v) >> 32); \ ++ (p)[5] = (unsigned char)((v) >> 40); \ ++ (p)[6] = (unsigned char)((v) >> 48); \ ++ (p)[7] = (unsigned char)((v) >> 56); \ ++} while (0) ++ ++#define SIPROUND() \ ++do { \ ++ v0 += v1; v1=ROTL64(v1,13); v1 ^= v0; v0=ROTL64(v0,32); \ ++ v2 += v3; v3=ROTL64(v3,16); v3 ^= v2; \ ++ v0 += v3; v3=ROTL64(v3,21); v3 ^= v0; \ ++ v2 += v1; v1=ROTL64(v1,17); v1 ^= v2; v2=ROTL64(v2,32); \ ++} while(0) ++ ++static apr_uint64_t ap_siphash24(const void *src, apr_size_t len, ++ const unsigned char key[AP_SIPHASH_KSIZE]) ++{ ++ const unsigned char *ptr, *end; ++ apr_uint64_t v0, v1, v2, v3, m; ++ apr_uint64_t k0, k1; ++ unsigned int rem; ++ ++ k0 = U8TO64_LE(key + 0); ++ k1 = U8TO64_LE(key + 8); ++ v3 = k1 ^ (apr_uint64_t)0x7465646279746573ULL; ++ v2 = k0 ^ (apr_uint64_t)0x6c7967656e657261ULL; ++ v1 = k1 ^ (apr_uint64_t)0x646f72616e646f6dULL; ++ v0 = k0 ^ (apr_uint64_t)0x736f6d6570736575ULL; ++ ++ rem = (unsigned int)(len & 0x7); ++ for (ptr = src, end = ptr + len - rem; ptr < end; ptr += 8) { ++ m = U8TO64_LE(ptr); ++ v3 ^= m; ++ SIPROUND(); ++ SIPROUND(); ++ v0 ^= m; ++ } ++ m = (apr_uint64_t)(len & 0xff) << 56; ++ switch (rem) { ++ case 7: m |= (apr_uint64_t)ptr[6] << 48; ++ case 6: m |= (apr_uint64_t)ptr[5] << 40; ++ case 5: m |= (apr_uint64_t)ptr[4] << 32; ++ case 4: m |= (apr_uint64_t)ptr[3] << 24; ++ case 3: m |= (apr_uint64_t)ptr[2] << 16; ++ case 2: m |= (apr_uint64_t)ptr[1] << 8; ++ case 1: m |= (apr_uint64_t)ptr[0]; ++ case 0: break; ++ } ++ v3 ^= m; ++ SIPROUND(); ++ SIPROUND(); ++ v0 ^= m; ++ ++ v2 ^= 0xff; ++ SIPROUND(); ++ SIPROUND(); ++ SIPROUND(); ++ SIPROUND(); ++ ++ return v0 ^ v1 ^ v2 ^ v3; ++} ++ ++static void ap_siphash24_auth(unsigned char out[AP_SIPHASH_DSIZE], ++ const void *src, apr_size_t len, ++ const unsigned char key[AP_SIPHASH_KSIZE]) ++{ ++ apr_uint64_t h; ++ h = ap_siphash24(src, len, key); ++ U64TO8_LE(out, h); ++} ++ ++static int ap_crypto_equals(const void *buf1, const void *buf2, ++ apr_size_t size) ++{ ++ const unsigned char *p1 = buf1; ++ const unsigned char *p2 = buf2; ++ unsigned char diff = 0; ++ apr_size_t i; ++ ++ for (i = 0; i < size; ++i) { ++ diff |= p1[i] ^ p2[i]; ++ } ++ ++ return 1 & ((diff - 1) >> 8); ++} ++ ++#endif ++ ++static void compute_auth(const void *src, apr_size_t len, ++ const char *passphrase, apr_size_t passlen, ++ unsigned char auth[AP_SIPHASH_DSIZE]) ++{ ++ unsigned char key[APR_MD5_DIGESTSIZE]; ++ ++ /* XXX: if we had a way to get the raw bytes from an apr_crypto_key_t ++ * we could use them directly (not available in APR-1.5.x). ++ * MD5 is 128bit too, so use it to get a suitable siphash key ++ * from the passphrase. ++ */ ++ apr_md5(key, passphrase, passlen); ++ ++ ap_siphash24_auth(auth, src, len, key); ++} ++ + /** + * Initialise the encryption as per the current config. + * +@@ -128,21 +269,14 @@ static apr_status_t encrypt_string(request_rec * r, const apr_crypto_t *f, + apr_crypto_block_t *block = NULL; + unsigned char *encrypt = NULL; + unsigned char *combined = NULL; +- apr_size_t encryptlen, tlen; ++ apr_size_t encryptlen, tlen, combinedlen; + char *base64; + apr_size_t blockSize = 0; + const unsigned char *iv = NULL; + apr_uuid_t salt; + apr_crypto_block_key_type_e *cipher; + const char *passphrase; +- +- /* by default, return an empty string */ +- *out = ""; +- +- /* don't attempt to encrypt an empty string, trying to do so causes a segfault */ +- if (!in || !*in) { +- return APR_SUCCESS; +- } ++ apr_size_t passlen; + + /* use a uuid as a salt value, and prepend it to our result */ + apr_uuid_get(&salt); +@@ -152,9 +286,9 @@ static apr_status_t encrypt_string(request_rec * r, const apr_crypto_t *f, + } + + /* encrypt using the first passphrase in the list */ +- passphrase = APR_ARRAY_IDX(dconf->passphrases, 0, char *); +- res = apr_crypto_passphrase(&key, &ivSize, passphrase, +- strlen(passphrase), ++ passphrase = APR_ARRAY_IDX(dconf->passphrases, 0, const char *); ++ passlen = strlen(passphrase); ++ res = apr_crypto_passphrase(&key, &ivSize, passphrase, passlen, + (unsigned char *) (&salt), sizeof(apr_uuid_t), + *cipher, APR_MODE_CBC, 1, 4096, f, r->pool); + if (APR_STATUS_IS_ENOKEY(res)) { +@@ -183,8 +317,9 @@ static apr_status_t encrypt_string(request_rec * r, const apr_crypto_t *f, + } + + /* encrypt the given string */ +- res = apr_crypto_block_encrypt(&encrypt, &encryptlen, (unsigned char *)in, +- strlen(in), block); ++ res = apr_crypto_block_encrypt(&encrypt, &encryptlen, ++ (const unsigned char *)in, strlen(in), ++ block); + if (APR_SUCCESS != res) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01830) + "apr_crypto_block_encrypt failed"); +@@ -198,18 +333,20 @@ static apr_status_t encrypt_string(request_rec * r, const apr_crypto_t *f, + } + encryptlen += tlen; + +- /* prepend the salt and the iv to the result */ +- combined = apr_palloc(r->pool, ivSize + encryptlen + sizeof(apr_uuid_t)); +- memcpy(combined, &salt, sizeof(apr_uuid_t)); +- memcpy(combined + sizeof(apr_uuid_t), iv, ivSize); +- memcpy(combined + sizeof(apr_uuid_t) + ivSize, encrypt, encryptlen); +- +- /* base64 encode the result */ +- base64 = apr_palloc(r->pool, apr_base64_encode_len(ivSize + encryptlen + +- sizeof(apr_uuid_t) + 1) +- * sizeof(char)); +- apr_base64_encode(base64, (const char *) combined, +- ivSize + encryptlen + sizeof(apr_uuid_t)); ++ /* prepend the salt and the iv to the result (keep room for the MAC) */ ++ combinedlen = AP_SIPHASH_DSIZE + sizeof(apr_uuid_t) + ivSize + encryptlen; ++ combined = apr_palloc(r->pool, combinedlen); ++ memcpy(combined + AP_SIPHASH_DSIZE, &salt, sizeof(apr_uuid_t)); ++ memcpy(combined + AP_SIPHASH_DSIZE + sizeof(apr_uuid_t), iv, ivSize); ++ memcpy(combined + AP_SIPHASH_DSIZE + sizeof(apr_uuid_t) + ivSize, ++ encrypt, encryptlen); ++ /* authenticate the whole salt+IV+ciphertext with a leading MAC */ ++ compute_auth(combined + AP_SIPHASH_DSIZE, combinedlen - AP_SIPHASH_DSIZE, ++ passphrase, passlen, combined); ++ ++ /* base64 encode the result (APR handles the trailing '\0') */ ++ base64 = apr_palloc(r->pool, apr_base64_encode_len(combinedlen)); ++ apr_base64_encode(base64, (const char *) combined, combinedlen); + *out = base64; + + return res; +@@ -234,6 +371,7 @@ static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f, + char *decoded; + apr_size_t blockSize = 0; + apr_crypto_block_key_type_e *cipher; ++ unsigned char auth[AP_SIPHASH_DSIZE]; + int i = 0; + + /* strip base64 from the string */ +@@ -241,6 +379,13 @@ static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f, + decodedlen = apr_base64_decode(decoded, in); + decoded[decodedlen] = '\0'; + ++ /* sanity check - decoded too short? */ ++ if (decodedlen < (AP_SIPHASH_DSIZE + sizeof(apr_uuid_t))) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO() ++ "too short to decrypt, aborting"); ++ return APR_ECRYPT; ++ } ++ + res = crypt_init(r, f, &cipher, dconf); + if (res != APR_SUCCESS) { + return res; +@@ -249,14 +394,25 @@ static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f, + /* try each passphrase in turn */ + for (; i < dconf->passphrases->nelts; i++) { + const char *passphrase = APR_ARRAY_IDX(dconf->passphrases, i, char *); +- apr_size_t len = decodedlen; +- char *slider = decoded; ++ apr_size_t passlen = strlen(passphrase); ++ apr_size_t len = decodedlen - AP_SIPHASH_DSIZE; ++ unsigned char *slider = (unsigned char *)decoded + AP_SIPHASH_DSIZE; ++ ++ /* Verify authentication of the whole salt+IV+ciphertext by computing ++ * the MAC and comparing it (timing safe) with the one in the payload. ++ */ ++ compute_auth(slider, len, passphrase, passlen, auth); ++ if (!ap_crypto_equals(auth, decoded, AP_SIPHASH_DSIZE)) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO() ++ "auth does not match, skipping"); ++ continue; ++ } + + /* encrypt using the first passphrase in the list */ +- res = apr_crypto_passphrase(&key, &ivSize, passphrase, +- strlen(passphrase), +- (unsigned char *)decoded, sizeof(apr_uuid_t), +- *cipher, APR_MODE_CBC, 1, 4096, f, r->pool); ++ res = apr_crypto_passphrase(&key, &ivSize, passphrase, passlen, ++ slider, sizeof(apr_uuid_t), ++ *cipher, APR_MODE_CBC, 1, 4096, ++ f, r->pool); + if (APR_STATUS_IS_ENOKEY(res)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01832) + "the passphrase '%s' was empty", passphrase); +@@ -279,7 +435,7 @@ static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f, + } + + /* sanity check - decoded too short? */ +- if (decodedlen < (sizeof(apr_uuid_t) + ivSize)) { ++ if (len < (sizeof(apr_uuid_t) + ivSize)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01836) + "too short to decrypt, skipping"); + res = APR_ECRYPT; +@@ -290,8 +446,8 @@ static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f, + slider += sizeof(apr_uuid_t); + len -= sizeof(apr_uuid_t); + +- res = apr_crypto_block_decrypt_init(&block, &blockSize, (unsigned char *)slider, key, +- r->pool); ++ res = apr_crypto_block_decrypt_init(&block, &blockSize, slider, key, ++ r->pool); + if (APR_SUCCESS != res) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01837) + "apr_crypto_block_decrypt_init failed"); +@@ -304,7 +460,7 @@ static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f, + + /* decrypt the given string */ + res = apr_crypto_block_decrypt(&decrypted, &decryptedlen, +- (unsigned char *)slider, len, block); ++ slider, len, block); + if (res) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01838) + "apr_crypto_block_decrypt failed"); + diff --git a/SOURCES/httpd-2.4.6-CVE-2016-2161.patch b/SOURCES/httpd-2.4.6-CVE-2016-2161.patch new file mode 100644 index 0000000..d45c2d9 --- /dev/null +++ b/SOURCES/httpd-2.4.6-CVE-2016-2161.patch @@ -0,0 +1,121 @@ +diff --git a/modules/aaa/mod_auth_digest.c b/modules/aaa/mod_auth_digest.c +index 44b5fc8..6a50ba7 100644 +--- a/modules/aaa/mod_auth_digest.c ++++ b/modules/aaa/mod_auth_digest.c +@@ -261,6 +261,26 @@ static void log_error_and_cleanup(char *msg, apr_status_t sts, server_rec *s) + cleanup_tables(NULL); + } + ++/* RMM helper functions that behave like single-step malloc/free. */ ++ ++static void *rmm_malloc(apr_rmm_t *rmm, apr_size_t size) ++{ ++ apr_rmm_off_t offset = apr_rmm_malloc(rmm, size); ++ ++ if (!offset) { ++ return NULL; ++ } ++ ++ return apr_rmm_addr_get(rmm, offset); ++} ++ ++static apr_status_t rmm_free(apr_rmm_t *rmm, void *alloc) ++{ ++ apr_rmm_off_t offset = apr_rmm_offset_get(rmm, alloc); ++ ++ return apr_rmm_free(rmm, offset); ++} ++ + #if APR_HAS_SHARED_MEMORY + + static int initialize_tables(server_rec *s, apr_pool_t *ctx) +@@ -299,8 +319,8 @@ static int initialize_tables(server_rec *s, apr_pool_t *ctx) + return !OK; + } + +- client_list = apr_rmm_addr_get(client_rmm, apr_rmm_malloc(client_rmm, sizeof(*client_list) + +- sizeof(client_entry*)*num_buckets)); ++ client_list = rmm_malloc(client_rmm, sizeof(*client_list) + ++ sizeof(client_entry *) * num_buckets); + if (!client_list) { + log_error_and_cleanup("failed to allocate shared memory", -1, s); + return !OK; +@@ -322,7 +342,7 @@ static int initialize_tables(server_rec *s, apr_pool_t *ctx) + + /* setup opaque */ + +- opaque_cntr = apr_rmm_addr_get(client_rmm, apr_rmm_malloc(client_rmm, sizeof(*opaque_cntr))); ++ opaque_cntr = rmm_malloc(client_rmm, sizeof(*opaque_cntr)); + if (opaque_cntr == NULL) { + log_error_and_cleanup("failed to allocate shared memory", -1, s); + return !OK; +@@ -339,7 +359,7 @@ static int initialize_tables(server_rec *s, apr_pool_t *ctx) + + /* setup one-time-nonce counter */ + +- otn_counter = apr_rmm_addr_get(client_rmm, apr_rmm_malloc(client_rmm, sizeof(*otn_counter))); ++ otn_counter = rmm_malloc(client_rmm, sizeof(*otn_counter)); + if (otn_counter == NULL) { + log_error_and_cleanup("failed to allocate shared memory", -1, s); + return !OK; +@@ -779,7 +799,7 @@ static client_entry *get_client(unsigned long key, const request_rec *r) + * last entry in each bucket and updates the counters. Returns the + * number of removed entries. + */ +-static long gc(void) ++static long gc(server_rec *s) + { + client_entry *entry, *prev; + unsigned long num_removed = 0, idx; +@@ -789,6 +809,12 @@ static long gc(void) + for (idx = 0; idx < client_list->tbl_len; idx++) { + entry = client_list->table[idx]; + prev = NULL; ++ ++ if (!entry) { ++ /* This bucket is empty. */ ++ continue; ++ } ++ + while (entry->next) { /* find last entry */ + prev = entry; + entry = entry->next; +@@ -800,8 +826,16 @@ static long gc(void) + client_list->table[idx] = NULL; + } + if (entry) { /* remove entry */ +- apr_rmm_free(client_rmm, apr_rmm_offset_get(client_rmm, entry)); ++ apr_status_t err; ++ ++ err = rmm_free(client_rmm, entry); + num_removed++; ++ ++ if (err) { ++ /* Nothing we can really do but log... */ ++ ap_log_error(APLOG_MARK, APLOG_ERR, err, s, APLOGNO() ++ "Failed to free auth_digest client allocation"); ++ } + } + } + +@@ -835,16 +869,16 @@ static client_entry *add_client(unsigned long key, client_entry *info, + + /* try to allocate a new entry */ + +- entry = apr_rmm_addr_get(client_rmm, apr_rmm_malloc(client_rmm, sizeof(client_entry))); ++ entry = rmm_malloc(client_rmm, sizeof(client_entry)); + if (!entry) { +- long num_removed = gc(); ++ long num_removed = gc(s); + ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(01766) + "gc'd %ld client entries. Total new clients: " + "%ld; Total removed clients: %ld; Total renewed clients: " + "%ld", num_removed, + client_list->num_created - client_list->num_renewed, + client_list->num_removed, client_list->num_renewed); +- entry = apr_rmm_addr_get(client_rmm, apr_rmm_malloc(client_rmm, sizeof(client_entry))); ++ entry = rmm_malloc(client_rmm, sizeof(client_entry)); + if (!entry) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01767) + "unable to allocate new auth_digest client"); + diff --git a/SOURCES/httpd-2.4.6-CVE-2016-8743.patch b/SOURCES/httpd-2.4.6-CVE-2016-8743.patch new file mode 100644 index 0000000..5a6fd58 --- /dev/null +++ b/SOURCES/httpd-2.4.6-CVE-2016-8743.patch @@ -0,0 +1,2124 @@ + +https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2016-8743 + +diff -uap httpd-2.4.6/docs/manual/mod/core.html.en.cve8743 httpd-2.4.6/docs/manual/mod/core.html.en +--- httpd-2.4.6/docs/manual/mod/core.html.en.cve8743 ++++ httpd-2.4.6/docs/manual/mod/core.html.en +@@ -67,6 +67,7 @@ +
  • ForceType
  • +
  • GprofDir
  • +
  • HostnameLookups
  • ++
  • HttpProtocolOptions
  • +
  • <If>
  • +
  • <IfDefine>
  • +
  • <IfModule>
  • +@@ -93,6 +94,7 @@ +
  • NameVirtualHost
  • +
  • Options
  • +
  • Protocol
  • ++
  • RegisterHttpMethod
  • +
  • RLimitCPU
  • +
  • RLimitMEM
  • +
  • RLimitNPROC
  • +@@ -1918,6 +1920,74 @@ + + +
    top
    ++

    HttpProtocolOptions Directive

    ++ ++ ++ ++ ++ ++ ++ ++ ++
    Description:Modify restrictions on HTTP Request Messages
    Syntax:HttpProtocolOptions [Strict|Unsafe] [RegisteredMethods|LenientMethods] ++ [Allow0.9|Require1.0]
    Default:HttpProtocolOptions Strict LenientMethods Allow0.9
    Context:server config, virtual host
    Status:Core
    Module:core
    Compatibility: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.

    ++ ++
    ++
    top
    +

    <If> Directive

    + +
    Description:Contains directives that apply only if a condition is +@@ -3541,6 +3611,23 @@ + + +
    top
    ++

    RegisterHttpMethod Directive

    ++ ++ ++ ++ ++ ++ ++
    Description:Register non-standard HTTP methods
    Syntax:RegisterHttpMethod method [method [...]]
    Context:server config
    Status:Core
    Module:core
    ++

    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.

    ++ ++
    ++
    top
    +

    RLimitCPU Directive

    + +
    Description:Limits the CPU consumption of processes launched +diff -uap httpd-2.4.6/include/http_core.h.cve8743 httpd-2.4.6/include/http_core.h +--- httpd-2.4.6/include/http_core.h.cve8743 ++++ httpd-2.4.6/include/http_core.h +@@ -668,6 +668,21 @@ + #define AP_MERGE_TRAILERS_DISABLE 2 + int merge_trailers; + ++#define AP_HTTP09_UNSET 0 ++#define AP_HTTP09_ENABLE 1 ++#define AP_HTTP09_DISABLE 2 ++ char http09_enable; ++ ++#define AP_HTTP_CONFORMANCE_UNSET 0 ++#define AP_HTTP_CONFORMANCE_UNSAFE 1 ++#define AP_HTTP_CONFORMANCE_STRICT 2 ++ char http_conformance; ++ ++#define AP_HTTP_METHODS_UNSET 0 ++#define AP_HTTP_METHODS_LENIENT 1 ++#define AP_HTTP_METHODS_REGISTERED 2 ++ char http_methods; ++ + } core_server_config; + + /* for AddOutputFiltersByType in core.c */ +diff -uap httpd-2.4.6/include/httpd.h.cve8743 httpd-2.4.6/include/httpd.h +--- httpd-2.4.6/include/httpd.h.cve8743 ++++ httpd-2.4.6/include/httpd.h +@@ -1584,6 +1584,28 @@ + */ + AP_DECLARE(int) ap_unescape_url(char *url); + ++/* Scan a string for field content chars, as defined by RFC7230 section 3.2 ++ * including VCHAR/obs-text, as well as HT and SP ++ * @param ptr The string to scan ++ * @return A pointer to the first (non-HT) ASCII ctrl character. ++ * @note lws and trailing whitespace are scanned, the caller is responsible ++ * for trimming leading and trailing whitespace ++ */ ++AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr); ++ ++/* Scan a string for token characters, as defined by RFC7230 section 3.2.6 ++ * @param ptr The string to scan ++ * @return A pointer to the first non-token character. ++ */ ++AP_DECLARE(const char *) ap_scan_http_token(const char *ptr); ++ ++/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+) ++ * and return a pointer to the first SP/CTL/NUL character encountered. ++ * @param ptr The string to scan ++ * @return A pointer to the first SP/CTL character. ++ */ ++AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr); ++ + /** + * Unescape a URL, but leaving %2f (slashes) escaped + * @param url The url to unescape +diff -uap httpd-2.4.6/include/http_protocol.h.cve8743 httpd-2.4.6/include/http_protocol.h +--- httpd-2.4.6/include/http_protocol.h.cve8743 ++++ httpd-2.4.6/include/http_protocol.h +@@ -582,17 +582,22 @@ + */ + AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri); + ++#define AP_GETLINE_FOLD 1 /* Whether to merge continuation lines */ ++#define AP_GETLINE_CRLF 2 /*Whether line ends must be in the form CR LF */ ++ + /** + * Get the next line of input for the request + * @param s The buffer into which to read the line + * @param n The size of the buffer + * @param r The request +- * @param fold Whether to merge continuation lines ++ * @param flags Bit flag of multiple parsing options ++ * AP_GETLINE_FOLD Whether to merge continuation lines ++ * AP_GETLINE_CRLF Whether line ends must be in the form CR LF + * @return The length of the line, if successful + * n, if the line is too big to fit in the buffer + * -1 for miscellaneous errors + */ +-AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold); ++AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags); + + /** + * Get the next line of input for the request +@@ -610,7 +615,9 @@ + * @param n The size of the buffer + * @param read The length of the line. + * @param r The request +- * @param fold Whether to merge continuation lines ++ * @param flags Bit flag of multiple parsing options ++ * AP_GETLINE_FOLD Whether to merge continuation lines ++ * AP_GETLINE_CRLF Whether line ends must be in the form CR LF + * @param bb Working brigade to use when reading buckets + * @return APR_SUCCESS, if successful + * APR_ENOSPC, if the line is too big to fit in the buffer +@@ -619,7 +626,7 @@ + #if APR_CHARSET_EBCDIC + AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, + apr_size_t *read, +- request_rec *r, int fold, ++ request_rec *r, int flags, + apr_bucket_brigade *bb); + #else /* ASCII box */ + #define ap_rgetline(s, n, read, r, fold, bb) \ +@@ -629,7 +636,7 @@ + /** @see ap_rgetline */ + AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + apr_size_t *read, +- request_rec *r, int fold, ++ request_rec *r, int flags, + apr_bucket_brigade *bb); + + /** +diff -uap httpd-2.4.6/modules/http/http_filters.c.cve8743 httpd-2.4.6/modules/http/http_filters.c +--- httpd-2.4.6/modules/http/http_filters.c.cve8743 ++++ httpd-2.4.6/modules/http/http_filters.c +@@ -126,14 +126,15 @@ + + /** + * Parse a chunk line with optional extension, detect overflow. +- * There are two error cases: +- * 1) If the conversion would require too many bits, APR_EGENERAL is returned. +- * 2) If the conversion used the correct number of bits, but an overflow ++ * There are several error cases: ++ * 1) If the chunk link is misformatted, APR_EINVAL is returned. ++ * 2) If the conversion would require too many bits, APR_EGENERAL is returned. ++ * 3) If the conversion used the correct number of bits, but an overflow + * caused only the sign bit to flip, then APR_ENOSPC is returned. +- * In general, any negative number can be considered an overflow error. ++ * A negative chunk length always indicates an overflow error. + */ + static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer, +- apr_size_t len, int linelimit) ++ apr_size_t len, int linelimit, int strict) + { + apr_size_t i = 0; + +@@ -146,6 +147,12 @@ + if (ctx->state == BODY_CHUNK_END + || ctx->state == BODY_CHUNK_END_LF) { + if (c == LF) { ++ if (strict && (ctx->state != BODY_CHUNK_END_LF)) { ++ /* ++ * CR missing before LF. ++ */ ++ return APR_EINVAL; ++ } + ctx->state = BODY_CHUNK; + } + else if (c == CR && ctx->state == BODY_CHUNK_END) { +@@ -153,7 +160,7 @@ + } + else { + /* +- * LF expected. ++ * CRLF expected. + */ + return APR_EINVAL; + } +@@ -180,6 +187,12 @@ + } + + if (c == LF) { ++ if (strict && (ctx->state != BODY_CHUNK_LF)) { ++ /* ++ * CR missing before LF. ++ */ ++ return APR_EINVAL; ++ } + if (ctx->remaining) { + ctx->state = BODY_CHUNK_DATA; + } +@@ -201,14 +214,17 @@ + } + else if (ctx->state == BODY_CHUNK_EXT) { + /* +- * Control chars (but tabs) are invalid. ++ * Control chars (excluding tabs) are invalid. ++ * TODO: more precisely limit input + */ + if (c != '\t' && apr_iscntrl(c)) { + return APR_EINVAL; + } + } + else if (c == ' ' || c == '\t') { +- /* Be lenient up to 10 BWS (term from rfc7230 - 3.2.3). ++ /* Be lenient up to 10 implied *LWS, a legacy of RFC 2616, ++ * and noted as errata to RFC7230; ++ * https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667 + */ + ctx->state = BODY_CHUNK_CR; + if (++ctx->chunk_bws > 10) { +@@ -324,7 +340,10 @@ + ap_input_mode_t mode, apr_read_type_e block, + apr_off_t readbytes) + { +- core_server_config *conf; ++ core_server_config *conf = ++ (core_server_config *) ap_get_module_config(f->r->server->module_config, ++ &core_module); ++ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + apr_bucket *e; + http_ctx_t *ctx = f->ctx; + apr_status_t rv; +@@ -332,9 +351,6 @@ + apr_bucket_brigade *bb; + int again; + +- conf = (core_server_config *) +- ap_get_module_config(f->r->server->module_config, &core_module); +- + /* just get out of the way of things we don't want. */ + if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) { + return ap_get_brigade(f->next, b, mode, block, readbytes); +@@ -526,7 +542,7 @@ + if (rv == APR_SUCCESS) { + parsing = 1; + rv = parse_chunk_size(ctx, buffer, len, +- f->r->server->limit_req_fieldsize); ++ f->r->server->limit_req_fieldsize, strict); + } + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r, APLOGNO(01590) +@@ -668,14 +684,121 @@ + return APR_SUCCESS; + } + ++struct check_header_ctx { ++ request_rec *r; ++ int strict; ++}; ++ ++/* check a single header, to be used with apr_table_do() */ ++static int check_header(struct check_header_ctx *ctx, ++ const char *name, const char **val) ++{ ++ const char *pos, *end; ++ char *dst = NULL; ++ ++ if (name[0] == '\0') { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02428) ++ "Empty response header name, aborting request"); ++ return 0; ++ } ++ ++ if (ctx->strict) { ++ end = ap_scan_http_token(name); ++ } ++ else { ++ end = ap_scan_vchar_obstext(name); ++ } ++ if (*end) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02429) ++ "Response header name '%s' contains invalid " ++ "characters, aborting request", ++ name); ++ return 0; ++ } ++ ++ for (pos = *val; *pos; pos = end) { ++ end = ap_scan_http_field_content(pos); ++ if (*end) { ++ if (end[0] != CR || end[1] != LF || (end[2] != ' ' && ++ end[2] != '\t')) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02430) ++ "Response header '%s' value of '%s' contains " ++ "invalid characters, aborting request", ++ name, pos); ++ return 0; ++ } ++ if (!dst) { ++ *val = dst = apr_palloc(ctx->r->pool, strlen(*val) + 1); ++ } ++ } ++ if (dst) { ++ memcpy(dst, pos, end - pos); ++ dst += end - pos; ++ if (*end) { ++ /* skip folding and replace with a single space */ ++ end += 3 + strspn(end + 3, "\t "); ++ *dst++ = ' '; ++ } ++ } ++ } ++ if (dst) { ++ *dst = '\0'; ++ } ++ return 1; ++} ++ ++static int check_headers_table(apr_table_t *t, struct check_header_ctx *ctx) ++{ ++ const apr_array_header_t *headers = apr_table_elts(t); ++ apr_table_entry_t *header; ++ int i; ++ ++ for (i = 0; i < headers->nelts; ++i) { ++ header = &APR_ARRAY_IDX(headers, i, apr_table_entry_t); ++ if (!header->key) { ++ continue; ++ } ++ if (!check_header(ctx, header->key, (const char **)&header->val)) { ++ return 0; ++ } ++ } ++ return 1; ++} ++ ++/** ++ * Check headers for HTTP conformance ++ * @return 1 if ok, 0 if bad ++ */ ++static APR_INLINE int check_headers(request_rec *r) ++{ ++ struct check_header_ctx ctx; ++ core_server_config *conf = ++ ap_get_core_module_config(r->server->module_config); ++ ++ ctx.r = r; ++ ctx.strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); ++ return check_headers_table(r->headers_out, &ctx) && ++ check_headers_table(r->err_headers_out, &ctx); ++} ++ ++static int check_headers_recursion(request_rec *r) ++{ ++ void *check = NULL; ++ apr_pool_userdata_get(&check, "check_headers_recursion", r->pool); ++ if (check) { ++ return 1; ++ } ++ apr_pool_userdata_setn("true", "check_headers_recursion", NULL, r->pool); ++ return 0; ++} ++ + typedef struct header_struct { + apr_pool_t *pool; + apr_bucket_brigade *bb; + } header_struct; + + /* Send a single HTTP header field to the client. Note that this function +- * is used in calls to table_do(), so their interfaces are co-dependent. +- * In other words, don't change this one without checking table_do in alloc.c. ++ * is used in calls to apr_table_do(), so don't change its interface. + * It returns true unless there was a write error of some kind. + */ + static int form_header_field(header_struct *h, +@@ -1160,6 +1283,7 @@ + + typedef struct header_filter_ctx { + int headers_sent; ++ int headers_error; + } header_filter_ctx; + + AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f, +@@ -1175,19 +1299,23 @@ + header_filter_ctx *ctx = f->ctx; + const char *ctype; + ap_bucket_error *eb = NULL; ++ apr_bucket *eos = NULL; + + AP_DEBUG_ASSERT(!r->main); + +- if (r->header_only) { +- if (!ctx) { +- ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx)); +- } +- else if (ctx->headers_sent) { ++ if (!ctx) { ++ ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx)); ++ } ++ if (ctx->headers_sent) { ++ /* Eat body if response must not have one. */ ++ if (r->header_only || r->status == HTTP_NO_CONTENT) { + apr_brigade_cleanup(b); +- return OK; ++ return APR_SUCCESS; + } + } +- ++ else if (!ctx->headers_error && !check_headers(r)) { ++ ctx->headers_error = 1; ++ } + for (e = APR_BRIGADE_FIRST(b); + e != APR_BRIGADE_SENTINEL(b); + e = APR_BUCKET_NEXT(e)) +@@ -1204,10 +1332,44 @@ + ap_remove_output_filter(f); + return ap_pass_brigade(f->next, b); + } ++ if (ctx->headers_error && APR_BUCKET_IS_EOS(e)) { ++ eos = e; ++ } + } +- if (eb) { +- int status; ++ if (ctx->headers_error) { ++ if (!eos) { ++ /* Eat body until EOS */ ++ apr_brigade_cleanup(b); ++ return APR_SUCCESS; ++ } + ++ /* We may come back here from ap_die() below, ++ * so clear anything from this response. ++ */ ++ ctx->headers_error = 0; ++ apr_table_clear(r->headers_out); ++ apr_table_clear(r->err_headers_out); ++ ++ /* Don't recall ap_die() if we come back here (from its own internal ++ * redirect or error response), otherwise we can end up in infinite ++ * recursion; better fall through with 500, minimal headers and an ++ * empty body (EOS only). ++ */ ++ if (!check_headers_recursion(r)) { ++ apr_brigade_cleanup(b); ++ ap_die(HTTP_INTERNAL_SERVER_ERROR, r); ++ return AP_FILTER_ERROR; ++ } ++ APR_BUCKET_REMOVE(eos); ++ apr_brigade_cleanup(b); ++ APR_BRIGADE_INSERT_TAIL(b, eos); ++ r->status = HTTP_INTERNAL_SERVER_ERROR; ++ r->content_type = r->content_encoding = NULL; ++ r->content_languages = NULL; ++ ap_set_content_length(r, 0); ++ } ++ else if (eb) { ++ int status; + status = eb->status; + apr_brigade_cleanup(b); + ap_die(status, r); +@@ -1264,6 +1426,10 @@ + apr_table_unset(r->headers_out, "Content-Length"); + } + ++ if (r->status == HTTP_NO_CONTENT) { ++ apr_table_unset(r->headers_out, "Content-Length"); ++ } ++ + ctype = ap_make_content_type(r, r->content_type); + if (ctype) { + apr_table_setn(r->headers_out, "Content-Type", ctype); +@@ -1352,11 +1518,11 @@ + terminate_header(b2); + + ap_pass_brigade(f->next, b2); ++ ctx->headers_sent = 1; + +- if (r->header_only) { ++ if (r->header_only || r->status == HTTP_NO_CONTENT) { + apr_brigade_cleanup(b); +- ctx->headers_sent = 1; +- return OK; ++ return APR_SUCCESS; + } + + r->sent_bodyct = 1; /* Whatever follows is real body stuff... */ +diff -uap httpd-2.4.6/server/core.c.cve8743 httpd-2.4.6/server/core.c +--- httpd-2.4.6/server/core.c.cve8743 ++++ httpd-2.4.6/server/core.c +@@ -506,6 +506,15 @@ + if (virt->trace_enable != AP_TRACE_UNSET) + conf->trace_enable = virt->trace_enable; + ++ if (virt->http09_enable != AP_HTTP09_UNSET) ++ conf->http09_enable = virt->http09_enable; ++ ++ if (virt->http_conformance != AP_HTTP_CONFORMANCE_UNSET) ++ conf->http_conformance = virt->http_conformance; ++ ++ if (virt->http_methods != AP_HTTP_METHODS_UNSET) ++ conf->http_methods = virt->http_methods; ++ + /* no action for virt->accf_map, not allowed per-vhost */ + + if (virt->protocol) +@@ -3632,6 +3641,57 @@ + return NULL; + } + ++static const char *set_http_protocol_options(cmd_parms *cmd, void *dummy, ++ const char *arg) ++{ ++ core_server_config *conf = ++ ap_get_core_module_config(cmd->server->module_config); ++ ++ if (strcasecmp(arg, "allow0.9") == 0) ++ conf->http09_enable |= AP_HTTP09_ENABLE; ++ else if (strcasecmp(arg, "require1.0") == 0) ++ conf->http09_enable |= AP_HTTP09_DISABLE; ++ else if (strcasecmp(arg, "strict") == 0) ++ conf->http_conformance |= AP_HTTP_CONFORMANCE_STRICT; ++ else if (strcasecmp(arg, "unsafe") == 0) ++ conf->http_conformance |= AP_HTTP_CONFORMANCE_UNSAFE; ++ else if (strcasecmp(arg, "registeredmethods") == 0) ++ conf->http_methods |= AP_HTTP_METHODS_REGISTERED; ++ else if (strcasecmp(arg, "lenientmethods") == 0) ++ conf->http_methods |= AP_HTTP_METHODS_LENIENT; ++ else ++ return "HttpProtocolOptions accepts " ++ "'Unsafe' or 'Strict' (default), " ++ "'RegisteredMethods' or 'LenientMethods' (default), and " ++ "'Require1.0' or 'Allow0.9' (default)"; ++ ++ if ((conf->http09_enable & AP_HTTP09_ENABLE) ++ && (conf->http09_enable & AP_HTTP09_DISABLE)) ++ return "HttpProtocolOptions 'Allow0.9' and 'Require1.0'" ++ " are mutually exclusive"; ++ ++ if ((conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) ++ && (conf->http_conformance & AP_HTTP_CONFORMANCE_UNSAFE)) ++ return "HttpProtocolOptions 'Strict' and 'Unsafe'" ++ " are mutually exclusive"; ++ ++ if ((conf->http_methods & AP_HTTP_METHODS_REGISTERED) ++ && (conf->http_methods & AP_HTTP_METHODS_LENIENT)) ++ return "HttpProtocolOptions 'RegisteredMethods' and 'LenientMethods'" ++ " are mutually exclusive"; ++ ++ return NULL; ++} ++ ++static const char *set_http_method(cmd_parms *cmd, void *conf, const char *arg) ++{ ++ const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); ++ if (err != NULL) ++ return err; ++ ap_method_register(cmd->pool, arg); ++ return NULL; ++} ++ + static apr_hash_t *errorlog_hash; + + static int log_constant_item(const ap_errorlog_info *info, const char *arg, +@@ -4143,6 +4203,13 @@ + "'on' (default), 'off' or 'extended' to trace request body content"), + AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF, + "merge request trailers into request headers or not"), ++AP_INIT_ITERATE("HttpProtocolOptions", set_http_protocol_options, NULL, RSRC_CONF, ++ "'Allow0.9' or 'Require1.0' (default); " ++ "'RegisteredMethods' or 'LenientMethods' (default); " ++ "'Unsafe' or 'Strict' (default). Sets HTTP acceptance rules") ++, ++AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF, ++ "Registers non-standard HTTP methods"), + { NULL } + }; + +diff -uap httpd-2.4.6/server/gen_test_char.c.cve8743 httpd-2.4.6/server/gen_test_char.c +--- httpd-2.4.6/server/gen_test_char.c.cve8743 ++++ httpd-2.4.6/server/gen_test_char.c +@@ -16,11 +16,11 @@ + + #ifdef CROSS_COMPILE + ++#include + #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 + +@@ -52,11 +52,13 @@ + #define T_ESCAPE_LOGITEM (0x10) + #define T_ESCAPE_FORENSIC (0x20) + #define T_ESCAPE_URLENCODED (0x40) ++#define T_HTTP_CTRLS (0x80) ++#define T_VCHAR_OBSTEXT (0x100) + + int main(int argc, char *argv[]) + { + unsigned c; +- unsigned char flags; ++ unsigned short flags; + + printf("/* this file is automatically generated by gen_test_char, " + "do not edit */\n" +@@ -67,19 +69,23 @@ + "#define T_ESCAPE_LOGITEM (%u)\n" + "#define T_ESCAPE_FORENSIC (%u)\n" + "#define T_ESCAPE_URLENCODED (%u)\n" ++ "#define T_HTTP_CTRLS (%u)\n" ++ "#define T_VCHAR_OBSTEXT (%u)\n" + "\n" +- "static const unsigned char test_char_table[256] = {", ++ "static const unsigned short test_char_table[256] = {", + T_ESCAPE_SHELL_CMD, + T_ESCAPE_PATH_SEGMENT, + T_OS_ESCAPE_PATH, + T_HTTP_TOKEN_STOP, + T_ESCAPE_LOGITEM, + T_ESCAPE_FORENSIC, +- T_ESCAPE_URLENCODED); ++ T_ESCAPE_URLENCODED, ++ T_HTTP_CTRLS, ++ T_VCHAR_OBSTEXT); + + for (c = 0; c < 256; ++c) { + flags = 0; +- if (c % 20 == 0) ++ if (c % 8 == 0) + printf("\n "); + + /* escape_shell_cmd */ +@@ -107,7 +113,7 @@ + flags |= T_ESCAPE_PATH_SEGMENT; + } + +- if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=/~", c)) { ++ if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:;@&=/~", c)) { + flags |= T_OS_ESCAPE_PATH; + } + +@@ -115,11 +121,32 @@ + flags |= T_ESCAPE_URLENCODED; + } + +- /* these are the "tspecials" (RFC2068) or "separators" (RFC2616) */ +- if (c && (apr_iscntrl(c) || strchr(" \t()<>@,;:\\\"/[]?={}", c))) { ++ /* Stop for any non-'token' character, including ctrls, obs-text, ++ * and "tspecials" (RFC2068) a.k.a. "separators" (RFC2616), which ++ * is easer to express as characters remaining in the ASCII token set ++ */ ++ if (!c || !(apr_isalnum(c) || strchr("!#$%&'*+-.^_`|~", c))) { + flags |= T_HTTP_TOKEN_STOP; + } + ++ /* Catch CTRLs other than VCHAR, HT and SP, and obs-text (RFC7230 3.2) ++ * This includes only the C0 plane, not C1 (which is obs-text itself.) ++ * XXX: We should verify that all ASCII C0 ctrls/DEL corresponding to ++ * the current EBCDIC translation are captured, and ASCII C1 ctrls ++ * corresponding are all permitted (as they fall under obs-text rule) ++ */ ++ if (!c || (apr_iscntrl(c) && c != '\t')) { ++ flags |= T_HTTP_CTRLS; ++ } ++ ++ /* From RFC3986, the specific sets of gen-delims, sub-delims (2.2), ++ * and unreserved (2.3) that are possible somewhere within a URI. ++ * Spec requires all others to be %XX encoded, including obs-text. ++ */ ++ if (c && !apr_iscntrl(c) && c != ' ') { ++ flags |= T_VCHAR_OBSTEXT; ++ } ++ + /* For logging, escape all control characters, + * double quotes (because they delimit the request in the log file) + * backslashes (because we use backslash for escaping) +@@ -137,7 +164,7 @@ + flags |= T_ESCAPE_FORENSIC; + } + +- printf("%u%c", flags, (c < 255) ? ',' : ' '); ++ printf("0x%03x%c", flags, (c < 255) ? ',' : ' '); + } + + printf("\n};\n"); +diff -uap httpd-2.4.6/server/protocol.c.cve8743 httpd-2.4.6/server/protocol.c +--- httpd-2.4.6/server/protocol.c.cve8743 ++++ httpd-2.4.6/server/protocol.c +@@ -189,6 +189,10 @@ + * caused by MIME folding (or broken clients) if fold != 0, and place it + * in the buffer s, of size n bytes, without the ending newline. + * ++ * Pulls from r->proto_input_filters instead of r->input_filters for ++ * stricter protocol adherence and better input filter behavior during ++ * chunked trailer processing (for http). ++ * + * If s is NULL, ap_rgetline_core will allocate necessary memory from r->pool. + * + * Returns APR_SUCCESS if there are no problems and sets *read to be +@@ -197,7 +201,7 @@ + * APR_ENOSPC is returned if there is not enough buffer space. + * Other errors may be returned on other errors. + * +- * The LF is *not* returned in the buffer. Therefore, a *read of 0 ++ * The [CR]LF are *not* returned in the buffer. Therefore, a *read of 0 + * indicates that an empty line was read. + * + * Notes: Because the buffer uses 1 char for NUL, the most we can return is +@@ -208,13 +212,15 @@ + */ + AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + apr_size_t *read, request_rec *r, +- int fold, apr_bucket_brigade *bb) ++ int flags, apr_bucket_brigade *bb) + { + apr_status_t rv; + apr_bucket *e; + apr_size_t bytes_handled = 0, current_alloc = 0; + char *pos, *last_char = *s; + int do_alloc = (*s == NULL), saw_eos = 0; ++ int fold = flags & AP_GETLINE_FOLD; ++ int crlf = flags & AP_GETLINE_CRLF; + + /* + * Initialize last_char as otherwise a random value will be compared +@@ -226,13 +232,15 @@ + + for (;;) { + apr_brigade_cleanup(bb); +- rv = ap_get_brigade(r->input_filters, bb, AP_MODE_GETLINE, ++ rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_GETLINE, + APR_BLOCK_READ, 0); + if (rv != APR_SUCCESS) { + return rv; + } + +- /* Something horribly wrong happened. Someone didn't block! */ ++ /* Something horribly wrong happened. Someone didn't block! ++ * (this also happens at the end of each keepalive connection) ++ */ + if (APR_BRIGADE_EMPTY(bb)) { + return APR_EGENERAL; + } +@@ -318,6 +326,13 @@ + } + } + ++ if (crlf && (last_char <= *s || last_char[-1] != APR_ASCII_CR)) { ++ *last_char = '\0'; ++ bytes_handled = last_char - *s; ++ *read = bytes_handled; ++ return APR_EINVAL; ++ } ++ + /* Now NUL-terminate the string at the end of the line; + * if the last-but-one character is a CR, terminate there */ + if (last_char > *s && last_char[-1] == APR_ASCII_CR) { +@@ -340,7 +355,7 @@ + apr_brigade_cleanup(bb); + + /* We only care about the first byte. */ +- rv = ap_get_brigade(r->input_filters, bb, AP_MODE_SPECULATIVE, ++ rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_SPECULATIVE, + APR_BLOCK_READ, 1); + if (rv != APR_SUCCESS) { + return rv; +@@ -391,7 +406,8 @@ + */ + if (do_alloc) { + tmp = NULL; +- } else { ++ } ++ else { + /* We're null terminated. */ + tmp = last_char; + } +@@ -461,7 +477,7 @@ + } + #endif + +-AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold) ++AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags) + { + char *tmp_s = s; + apr_status_t rv; +@@ -469,7 +485,7 @@ + apr_bucket_brigade *tmp_bb; + + tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); +- rv = ap_rgetline(&tmp_s, n, &len, r, fold, tmp_bb); ++ rv = ap_rgetline(&tmp_s, n, &len, r, flags, tmp_bb); + apr_brigade_destroy(tmp_bb); + + /* Map the out-of-space condition to the old API. */ +@@ -549,16 +565,29 @@ + } + } + +-static int read_request_line(request_rec *r, apr_bucket_brigade *bb) ++/* get the length of the field name for logging, but no more than 80 bytes */ ++#define LOG_NAME_MAX_LEN 80 ++static int field_name_len(const char *field) + { +- const char *ll; +- const char *uri; +- const char *pro; ++ const char *end = ap_strchr_c(field, ':'); ++ if (end == NULL || end - field > LOG_NAME_MAX_LEN) ++ return LOG_NAME_MAX_LEN; ++ return end - field; ++} + +- int major = 1, minor = 0; /* Assume HTTP/1.0 if non-"HTTP" protocol */ +- char http[5]; ++static int read_request_line(request_rec *r, apr_bucket_brigade *bb) ++{ ++ enum { ++ rrl_none, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace, ++ rrl_missinguri, rrl_baduri, rrl_badprotocol, rrl_trailingtext, ++ rrl_badmethod09, rrl_reject09 ++ } deferred_error = rrl_none; ++ char *ll; ++ char *uri; + apr_size_t len; + int num_blank_lines = 0; ++ core_server_config *conf = ap_get_core_module_config(r->server->module_config); ++ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + int max_blank_lines = r->server->limit_req_fields; + + if (max_blank_lines <= 0) { +@@ -588,7 +617,7 @@ + */ + r->the_request = NULL; + rv = ap_rgetline(&(r->the_request), (apr_size_t)(r->server->limit_req_line + 2), +- &len, r, 0, bb); ++ &len, r, strict ? AP_GETLINE_CRLF : 0, bb); + + if (rv != APR_SUCCESS) { + r->request_time = apr_time_now(); +@@ -599,8 +628,6 @@ + */ + if (APR_STATUS_IS_ENOSPC(rv)) { + r->status = HTTP_REQUEST_URI_TOO_LARGE; +- r->proto_num = HTTP_VERSION(1,0); +- r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); + } + else if (APR_STATUS_IS_TIMEUP(rv)) { + r->status = HTTP_REQUEST_TIME_OUT; +@@ -608,6 +635,8 @@ + else if (APR_STATUS_IS_EINVAL(rv)) { + r->status = HTTP_BAD_REQUEST; + } ++ r->proto_num = HTTP_VERSION(1,0); ++ r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); + return 0; + } + } while ((len <= 0) && (++num_blank_lines < max_blank_lines)); +@@ -619,46 +648,263 @@ + } + + r->request_time = apr_time_now(); +- ll = r->the_request; +- r->method = ap_getword_white(r->pool, &ll); ++ r->method = r->the_request; ++ ++ /* If there is whitespace before a method, skip it and mark in error */ ++ if (apr_isspace(*r->method)) { ++ deferred_error = rrl_badwhitespace; ++ for ( ; apr_isspace(*r->method); ++r->method) ++ ; ++ } + +- uri = ap_getword_white(r->pool, &ll); ++ /* Scan the method up to the next whitespace, ensure it contains only ++ * valid http-token characters, otherwise mark in error ++ */ ++ if (strict) { ++ ll = (char*) ap_scan_http_token(r->method); ++ } ++ else { ++ ll = (char*) ap_scan_vchar_obstext(r->method); ++ } + +- /* Provide quick information about the request method as soon as known */ ++ if (((ll == r->method) || (*ll && !apr_isspace(*ll))) ++ && deferred_error == rrl_none) { ++ deferred_error = rrl_badmethod; ++ ll = strpbrk(ll, "\t\n\v\f\r "); ++ } + +- r->method_number = ap_method_number_of(r->method); +- if (r->method_number == M_GET && r->method[0] == 'H') { +- r->header_only = 1; ++ /* Verify method terminated with a single SP, or mark as specific error */ ++ if (!ll) { ++ if (deferred_error == rrl_none) ++ deferred_error = rrl_missinguri; ++ r->protocol = uri = ""; ++ len = 0; ++ goto rrl_done; ++ } ++ else if (strict && ll[0] && apr_isspace(ll[1]) ++ && deferred_error == rrl_none) { ++ deferred_error = rrl_excesswhitespace; + } + +- ap_parse_uri(r, uri); ++ /* Advance uri pointer over leading whitespace, NUL terminate the method ++ * If non-SP whitespace is encountered, mark as specific error ++ */ ++ for (uri = ll; apr_isspace(*uri); ++uri) ++ if (*uri != ' ' && deferred_error == rrl_none) ++ deferred_error = rrl_badwhitespace; ++ *ll = '\0'; ++ ++ if (!*uri && deferred_error == rrl_none) ++ deferred_error = rrl_missinguri; ++ ++ /* Scan the URI up to the next whitespace, ensure it contains no raw ++ * control characters, otherwise mark in error ++ */ ++ ll = (char*) ap_scan_vchar_obstext(uri); ++ if (ll == uri || (*ll && !apr_isspace(*ll))) { ++ deferred_error = rrl_baduri; ++ ll = strpbrk(ll, "\t\n\v\f\r "); ++ } + +- if (ll[0]) { ++ /* Verify URI terminated with a single SP, or mark as specific error */ ++ if (!ll) { ++ r->protocol = ""; ++ len = 0; ++ goto rrl_done; ++ } ++ else if (strict && ll[0] && apr_isspace(ll[1]) ++ && deferred_error == rrl_none) { ++ deferred_error = rrl_excesswhitespace; ++ } ++ ++ /* Advance protocol pointer over leading whitespace, NUL terminate the uri ++ * If non-SP whitespace is encountered, mark as specific error ++ */ ++ for (r->protocol = ll; apr_isspace(*r->protocol); ++r->protocol) ++ if (*r->protocol != ' ' && deferred_error == rrl_none) ++ deferred_error = rrl_badwhitespace; ++ *ll = '\0'; ++ ++ /* Scan the protocol up to the next whitespace, validation comes later */ ++ if (!(ll = (char*) ap_scan_vchar_obstext(r->protocol))) { ++ len = strlen(r->protocol); ++ goto rrl_done; ++ } ++ len = ll - r->protocol; ++ ++ /* Advance over trailing whitespace, if found mark in error, ++ * determine if trailing text is found, unconditionally mark in error, ++ * finally NUL terminate the protocol string ++ */ ++ if (*ll && !apr_isspace(*ll)) { ++ deferred_error = rrl_badprotocol; ++ } ++ else if (strict && *ll) { ++ deferred_error = rrl_excesswhitespace; ++ } ++ else { ++ for ( ; apr_isspace(*ll); ++ll) ++ if (*ll != ' ' && deferred_error == rrl_none) ++ deferred_error = rrl_badwhitespace; ++ if (*ll && deferred_error == rrl_none) ++ deferred_error = rrl_trailingtext; ++ } ++ *((char *)r->protocol + len) = '\0'; ++ ++rrl_done: ++ /* For internal integrety and palloc efficiency, reconstruct the_request ++ * in one palloc, using only single SP characters, per spec. ++ */ ++ r->the_request = apr_pstrcat(r->pool, r->method, *uri ? " " : NULL, uri, ++ *r->protocol ? " " : NULL, r->protocol, NULL); ++ ++ if (len == 8 ++ && r->protocol[0] == 'H' && r->protocol[1] == 'T' ++ && r->protocol[2] == 'T' && r->protocol[3] == 'P' ++ && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) ++ && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) ++ && r->protocol[5] != '0') { ++ r->assbackwards = 0; ++ r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); ++ } ++ else if (len == 8 ++ && (r->protocol[0] == 'H' || r->protocol[0] == 'h') ++ && (r->protocol[1] == 'T' || r->protocol[1] == 't') ++ && (r->protocol[2] == 'T' || r->protocol[2] == 't') ++ && (r->protocol[3] == 'P' || r->protocol[3] == 'p') ++ && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) ++ && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) ++ && r->protocol[5] != '0') { + r->assbackwards = 0; +- pro = ll; +- len = strlen(ll); +- } else { ++ r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); ++ if (strict && deferred_error == rrl_none) ++ deferred_error = rrl_badprotocol; ++ else ++ memcpy((char*)r->protocol, "HTTP", 4); ++ } else if (r->protocol[0]) { ++ r->proto_num = HTTP_VERSION(0, 9); ++ /* Defer setting the r->protocol string till error msg is composed */ ++ if (deferred_error == rrl_none) ++ deferred_error = rrl_badprotocol; ++ } ++ else { + r->assbackwards = 1; +- pro = "HTTP/0.9"; +- len = 8; ++ r->protocol = apr_pstrdup(r->pool, "HTTP/0.9"); ++ r->proto_num = HTTP_VERSION(0, 9); + } +- r->protocol = apr_pstrmemdup(r->pool, pro, len); + +- /* Avoid sscanf in the common case */ +- if (len == 8 +- && pro[0] == 'H' && pro[1] == 'T' && pro[2] == 'T' && pro[3] == 'P' +- && pro[4] == '/' && apr_isdigit(pro[5]) && pro[6] == '.' +- && apr_isdigit(pro[7])) { +- r->proto_num = HTTP_VERSION(pro[5] - '0', pro[7] - '0'); +- } +- else if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor) +- && (strcasecmp("http", http) == 0) +- && (minor < HTTP_VERSION(1, 0)) ) /* don't allow HTTP/0.1000 */ +- r->proto_num = HTTP_VERSION(major, minor); +- else +- r->proto_num = HTTP_VERSION(1, 0); ++ /* Determine the method_number and parse the uri prior to invoking error ++ * handling, such that these fields are available for subsitution ++ */ ++ r->method_number = ap_method_number_of(r->method); ++ if (r->method_number == M_GET && r->method[0] == 'H') ++ r->header_only = 1; ++ ++ ap_parse_uri(r, uri); ++ ++ /* With the request understood, we can consider HTTP/0.9 specific errors */ ++ if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) { ++ if (conf->http09_enable == AP_HTTP09_DISABLE) ++ deferred_error = rrl_reject09; ++ else if (strict && (r->method_number != M_GET || r->header_only)) ++ deferred_error = rrl_badmethod09; ++ } ++ ++ /* Now that the method, uri and protocol are all processed, ++ * we can safely resume any deferred error reporting ++ */ ++ if (deferred_error != rrl_none) { ++ if (deferred_error == rrl_badmethod) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03445) ++ "HTTP Request Line; Invalid method token: '%.*s'", ++ field_name_len(r->method), r->method); ++ else if (deferred_error == rrl_badmethod09) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03444) ++ "HTTP Request Line; Invalid method token: '%.*s'" ++ " (only GET is allowed for HTTP/0.9 requests)", ++ field_name_len(r->method), r->method); ++ else if (deferred_error == rrl_missinguri) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03446) ++ "HTTP Request Line; Missing URI"); ++ else if (deferred_error == rrl_baduri) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03454) ++ "HTTP Request Line; URI incorrectly encoded: '%.*s'", ++ field_name_len(r->uri), r->uri); ++ else if (deferred_error == rrl_badwhitespace) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03447) ++ "HTTP Request Line; Invalid whitespace"); ++ else if (deferred_error == rrl_excesswhitespace) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03448) ++ "HTTP Request Line; Excess whitespace " ++ "(disallowed by HttpProtocolOptions Strict"); ++ else if (deferred_error == rrl_trailingtext) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03449) ++ "HTTP Request Line; Extraneous text found '%.*s' " ++ "(perhaps whitespace was injected?)", ++ field_name_len(ll), ll); ++ else if (deferred_error == rrl_reject09) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02401) ++ "HTTP Request Line; Rejected HTTP/0.9 request"); ++ else if (deferred_error == rrl_badprotocol) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418) ++ "HTTP Request Line; Unrecognized protocol '%.*s' " ++ "(perhaps whitespace was injected?)", ++ field_name_len(r->protocol), r->protocol); ++ r->status = HTTP_BAD_REQUEST; ++ goto rrl_failed; ++ } ++ ++ if (conf->http_methods == AP_HTTP_METHODS_REGISTERED ++ && r->method_number == M_INVALID) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423) ++ "HTTP Request Line; Unrecognized HTTP method: '%.*s' " ++ "(disallowed by RegisteredMethods)", ++ field_name_len(r->method), r->method); ++ r->status = HTTP_NOT_IMPLEMENTED; ++ /* This can't happen in an HTTP/0.9 request, we verified GET above */ ++ return 0; ++ } ++ ++ if (r->status != HTTP_OK) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03450) ++ "HTTP Request Line; Unable to parse URI: '%.*s'", ++ field_name_len(r->uri), r->uri); ++ goto rrl_failed; ++ } ++ ++ if (strict) { ++ if (r->parsed_uri.fragment) { ++ /* RFC3986 3.5: no fragment */ ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02421) ++ "HTTP Request Line; URI must not contain a fragment"); ++ r->status = HTTP_BAD_REQUEST; ++ goto rrl_failed; ++ } ++ if (r->parsed_uri.user || r->parsed_uri.password) { ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02422) ++ "HTTP Request Line; URI must not contain a " ++ "username/password"); ++ r->status = HTTP_BAD_REQUEST; ++ goto rrl_failed; ++ } ++ } + + return 1; ++rrl_failed: ++ if (r->proto_num == HTTP_VERSION(0, 9)) { ++ /* Send all parsing and protocol error response with 1.x behavior, ++ * and reserve 505 errors for actual HTTP protocols presented. ++ * As called out in RFC7230 3.5, any errors parsing the protocol ++ * from the request line are nearly always misencoded HTTP/1.x ++ * requests. Only a valid 0.9 request with no parsing errors ++ * at all may be treated as a simple request, if allowed. ++ */ ++ r->assbackwards = 0; ++ r->connection->keepalive = AP_CONN_CLOSE; ++ r->proto_num = HTTP_VERSION(1, 0); ++ r->protocol = apr_pstrdup(r->pool, "HTTP/1.0"); ++ } ++ return 0; + } + + static int table_do_fn_check_lengths(void *r_, const char *key, +@@ -670,26 +916,13 @@ + + r->status = HTTP_BAD_REQUEST; + apr_table_setn(r->notes, "error-notes", +- apr_pstrcat(r->pool, "Size of a request header field " +- "after merging exceeds server limit.
    " +- "\n
    \n",
    +-                               ap_escape_html(r->pool, key),
    +-                               "
    \n", NULL)); +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request header " +- "exceeds LimitRequestFieldSize after merging: %s", key); ++ "Size of a request header field exceeds server limit."); ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request " ++ "header exceeds LimitRequestFieldSize after merging: %.*s", ++ field_name_len(key), key); + return 0; + } + +-/* get the length of the field name for logging, but no more than 80 bytes */ +-#define LOG_NAME_MAX_LEN 80 +-static int field_name_len(const char *field) +-{ +- const char *end = ap_strchr_c(field, ':'); +- if (end == NULL || end - field > LOG_NAME_MAX_LEN) +- return LOG_NAME_MAX_LEN; +- return end - field; +-} +- + AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb) + { + char *last_field = NULL; +@@ -700,6 +933,8 @@ + apr_size_t len; + int fields_read = 0; + char *tmp_field; ++ core_server_config *conf = ap_get_core_module_config(r->server->module_config); ++ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + + /* + * Read header lines until we get the empty separator line, a read error, +@@ -707,11 +942,10 @@ + */ + while(1) { + apr_status_t rv; +- int folded = 0; + + field = NULL; + rv = ap_rgetline(&field, r->server->limit_req_fieldsize + 2, +- &len, r, 0, bb); ++ &len, r, strict ? AP_GETLINE_CRLF : 0, bb); + + if (rv != APR_SUCCESS) { + if (APR_STATUS_IS_TIMEUP(rv)) { +@@ -728,153 +962,217 @@ + * exceeds the configured limit for a field size. + */ + if (rv == APR_ENOSPC) { +- const char *field_escaped; +- if (field) { +- /* ensure ap_escape_html will terminate correctly */ +- field[len - 1] = '\0'; +- field_escaped = ap_escape_html(r->pool, field); +- } +- else { +- field_escaped = field = ""; +- } +- + apr_table_setn(r->notes, "error-notes", +- apr_psprintf(r->pool, +- "Size of a request header field " +- "exceeds server limit.
    \n" +- "
    \n%.*s\n
    \n", +- field_name_len(field_escaped), +- field_escaped)); ++ "Size of a request header field " ++ "exceeds server limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00561) + "Request header exceeds LimitRequestFieldSize%s" + "%.*s", +- *field ? ": " : "", +- field_name_len(field), field); ++ (field && *field) ? ": " : "", ++ (field) ? field_name_len(field) : 0, ++ (field) ? field : ""); + } + return; + } + +- if (last_field != NULL) { +- if ((len > 0) && ((*field == '\t') || *field == ' ')) { +- /* This line is a continuation of the preceding line(s), +- * so append it to the line that we've set aside. +- * Note: this uses a power-of-two allocator to avoid +- * doing O(n) allocs and using O(n^2) space for +- * continuations that span many many lines. +- */ +- apr_size_t fold_len = last_len + len + 1; /* trailing null */ + +- if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { +- r->status = HTTP_BAD_REQUEST; +- /* report what we have accumulated so far before the +- * overflow (last_field) as the field with the problem +- */ +- apr_table_setn(r->notes, "error-notes", +- apr_psprintf(r->pool, +- "Size of a request header field " +- "after folding " +- "exceeds server limit.
    \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, APLOGNO(00562) +- "Request header exceeds LimitRequestFieldSize " +- "after folding: %.*s", +- field_name_len(last_field), last_field); +- return; +- } ++ /* For all header values, and all obs-fold lines, the presence of ++ * additional whitespace is a no-op, so collapse trailing whitespace ++ * to save buffer allocation and optimize copy operations. ++ * Do not remove the last single whitespace under any condition. ++ */ ++ while (len > 1 && (field[len-1] == '\t' || field[len-1] == ' ')) { ++ field[--len] = '\0'; ++ } ++ ++ if (*field == '\t' || *field == ' ') { ++ /* Append any newly-read obs-fold line onto the preceding ++ * last_field line we are processing ++ */ ++ apr_size_t fold_len; + ++ if (last_field == NULL) { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03442) ++ "Line folding encountered before first" ++ " header line"); ++ return; ++ } ++ ++ if (field[1] == '\0') { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03443) ++ "Empty folded line encountered"); ++ return; ++ } ++ ++ /* Leading whitespace on an obs-fold line can be ++ * similarly discarded */ ++ while (field[1] == '\t' || field[1] == ' ') { ++ ++field; --len; ++ } ++ ++ /* This line is a continuation of the preceding line(s), ++ * so append it to the line that we've set aside. ++ * Note: this uses a power-of-two allocator to avoid ++ * doing O(n) allocs and using O(n^2) space for ++ * continuations that span many many lines. ++ */ ++ fold_len = last_len + len + 1; /* trailing null */ ++ ++ if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { ++ r->status = HTTP_BAD_REQUEST; ++ /* report what we have accumulated so far before the ++ * overflow (last_field) as the field with the problem ++ */ ++ apr_table_setn(r->notes, "error-notes", ++ "Size of a request header field " ++ "exceeds server limit."); ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562) ++ "Request header exceeds LimitRequestFieldSize " ++ "after folding: %.*s", ++ field_name_len(last_field), last_field); ++ return; ++ } ++ ++ if (fold_len > alloc_len) { ++ char *fold_buf; ++ alloc_len += alloc_len; + if (fold_len > alloc_len) { +- char *fold_buf; +- alloc_len += alloc_len; +- if (fold_len > alloc_len) { +- alloc_len = fold_len; +- } +- fold_buf = (char *)apr_palloc(r->pool, alloc_len); +- memcpy(fold_buf, last_field, last_len); +- last_field = fold_buf; ++ alloc_len = fold_len; + } +- memcpy(last_field + last_len, field, len +1); /* +1 for nul */ +- last_len += len; +- folded = 1; +- } +- else /* not a continuation line */ { ++ fold_buf = (char *)apr_palloc(r->pool, alloc_len); ++ memcpy(fold_buf, last_field, last_len); ++ last_field = fold_buf; ++ } ++ memcpy(last_field + last_len, field, len +1); /* +1 for nul */ ++ /* Replace obs-fold w/ SP per RFC 7230 3.2.4 */ ++ last_field[last_len] = ' '; ++ last_len += len; + +- if (r->server->limit_req_fields ++ /* We've appended this obs-fold line to last_len, proceed to ++ * read the next input line ++ */ ++ continue; ++ } ++ else if (last_field != NULL) { ++ /* Process the previous last_field header line with all obs-folded ++ * segments already concatinated (this is not operating on the ++ * most recently read input line). ++ */ ++ if (r->server->limit_req_fields + && (++fields_read > r->server->limit_req_fields)) { +- r->status = HTTP_BAD_REQUEST; +- apr_table_setn(r->notes, "error-notes", +- "The number of request header fields " +- "exceeds this server's limit."); +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563) +- "Number of request headers exceeds " +- "LimitRequestFields"); +- return; +- } ++ r->status = HTTP_BAD_REQUEST; ++ apr_table_setn(r->notes, "error-notes", ++ "The number of request header fields " ++ "exceeds this server's limit."); ++ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563) ++ "Number of request headers exceeds " ++ "LimitRequestFields"); ++ return; ++ } + +- if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ +- r->status = HTTP_BAD_REQUEST; /* abort bad request */ +- apr_table_setn(r->notes, "error-notes", +- apr_psprintf(r->pool, +- "Request header field is " +- "missing ':' separator.
    \n" +- "
    \n%.*s
    \n", +- (int)LOG_NAME_MAX_LEN, +- ap_escape_html(r->pool, +- last_field))); +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00564) ++ if (!strict) ++ { ++ /* Not Strict ('Unsafe' mode), using the legacy parser */ ++ ++ if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ ++ r->status = HTTP_BAD_REQUEST; /* abort bad request */ ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00564) + "Request header field is missing ':' " + "separator: %.*s", (int)LOG_NAME_MAX_LEN, + last_field); ++ + return; + } + +- tmp_field = value - 1; /* last character of field-name */ ++ /* last character of field-name */ ++ tmp_field = value - (value > last_field ? 1 : 0); + + *value++ = '\0'; /* NUL-terminate at colon */ + ++ if (strpbrk(last_field, "\t\n\v\f\r ")) { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03452) ++ "Request header field name presented" ++ " invalid whitespace"); ++ return; ++ } ++ + while (*value == ' ' || *value == '\t') { +- ++value; /* Skip to start of value */ ++ ++value; /* Skip to start of value */ ++ } ++ ++ if (strpbrk(value, "\n\v\f\r")) { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03451) ++ "Request header field value presented" ++ " bad whitespace"); ++ return; + } + +- /* Strip LWS after field-name: */ +- while (tmp_field > last_field +- && (*tmp_field == ' ' || *tmp_field == '\t')) { +- *tmp_field-- = '\0'; ++ if (tmp_field == last_field) { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03453) ++ "Request header field name was empty"); ++ return; ++ } ++ } ++ else /* Using strict RFC7230 parsing */ ++ { ++ /* Ensure valid token chars before ':' per RFC 7230 3.2.4 */ ++ value = (char *)ap_scan_http_token(last_field); ++ if ((value == last_field) || *value != ':') { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02426) ++ "Request header field name is malformed: " ++ "%.*s", (int)LOG_NAME_MAX_LEN, last_field); ++ return; + } + +- /* Strip LWS after field-value: */ +- tmp_field = last_field + last_len - 1; +- while (tmp_field > value +- && (*tmp_field == ' ' || *tmp_field == '\t')) { +- *tmp_field-- = '\0'; ++ *value++ = '\0'; /* NUL-terminate last_field name at ':' */ ++ ++ while (*value == ' ' || *value == '\t') { ++ ++value; /* Skip LWS of value */ + } + +- apr_table_addn(r->headers_in, last_field, value); ++ /* Find invalid, non-HT ctrl char, or the trailing NULL */ ++ tmp_field = (char *)ap_scan_http_field_content(value); + +- /* reset the alloc_len so that we'll allocate a new +- * buffer if we have to do any more folding: we can't +- * use the previous buffer because its contents are +- * now part of r->headers_in ++ /* Reject value for all garbage input (CTRLs excluding HT) ++ * e.g. only VCHAR / SP / HT / obs-text are allowed per ++ * RFC7230 3.2.6 - leave all more explicit rule enforcement ++ * for specific header handler logic later in the cycle + */ +- alloc_len = 0; ++ if (*tmp_field != '\0') { ++ r->status = HTTP_BAD_REQUEST; ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02427) ++ "Request header value is malformed: " ++ "%.*s", (int)LOG_NAME_MAX_LEN, value); ++ return; ++ } ++ } ++ ++ apr_table_addn(r->headers_in, last_field, value); + +- } /* end if current line is not a continuation starting with tab */ ++ /* This last_field header is now stored in headers_in, ++ * resume processing of the current input line. ++ */ + } + +- /* Found a blank line, stop. */ ++ /* Found the terminating empty end-of-headers line, stop. */ + if (len == 0) { + break; + } + +- /* Keep track of this line so that we can parse it on +- * the next loop iteration. (In the folded case, last_field +- * has been updated already.) ++ /* Keep track of this new header line so that we can extend it across ++ * any obs-fold or parse it on the next loop iteration. We referenced ++ * our previously allocated buffer in r->headers_in, ++ * so allocate a fresh buffer if required. + */ +- if (!folded) { +- last_field = field; +- last_len = len; +- } ++ alloc_len = 0; ++ last_field = field; ++ last_len = len; + } + + /* Combine multiple message-header fields with the same +@@ -899,7 +1197,7 @@ + request_rec *r; + apr_pool_t *p; + const char *expect; +- int access_status = HTTP_OK; ++ int access_status; + apr_bucket_brigade *tmp_bb; + apr_socket_t *csd; + apr_interval_time_t cur_timeout; +@@ -958,35 +1256,39 @@ + + /* Get the request... */ + if (!read_request_line(r, tmp_bb)) { +- if (r->status == HTTP_REQUEST_URI_TOO_LARGE +- || r->status == HTTP_BAD_REQUEST) { ++ switch (r->status) { ++ case HTTP_REQUEST_URI_TOO_LARGE: ++ case HTTP_BAD_REQUEST: ++ case HTTP_VERSION_NOT_SUPPORTED: ++ case HTTP_NOT_IMPLEMENTED: + if (r->status == HTTP_REQUEST_URI_TOO_LARGE) { + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00565) + "request failed: client's request-line exceeds LimitRequestLine (longer than %d)", + r->server->limit_req_line); + } + else if (r->method == NULL) { +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00566) +- "request failed: invalid characters in URI"); ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00566) ++ "request failed: malformed request line"); + } +- ap_send_error_response(r, 0); ++ access_status = r->status; ++ r->status = HTTP_OK; ++ ap_die(access_status, r); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); + ap_run_log_transaction(r); ++ r = NULL; + apr_brigade_destroy(tmp_bb); + goto traceout; +- } +- else if (r->status == HTTP_REQUEST_TIME_OUT) { +- ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); +- if (!r->connection->keepalives) { ++ case HTTP_REQUEST_TIME_OUT: ++ ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL); ++ if (!r->connection->keepalives) + ap_run_log_transaction(r); +- } + apr_brigade_destroy(tmp_bb); + goto traceout; ++ default: ++ apr_brigade_destroy(tmp_bb); ++ r = NULL; ++ goto traceout; + } +- +- apr_brigade_destroy(tmp_bb); +- r = NULL; +- goto traceout; + } + + /* We may have been in keep_alive_timeout mode, so toggle back +@@ -1003,7 +1305,7 @@ + if (!r->assbackwards) { + ap_get_mime_headers_core(r, tmp_bb); + if (r->status != HTTP_OK) { +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00567) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567) + "request failed: error reading the headers"); + ap_send_error_response(r, 0); + ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); +@@ -1021,25 +1323,6 @@ + apr_table_unset(r->headers_in, "Content-Length"); + } + } +- else { +- if (r->header_only) { +- /* +- * Client asked for headers only with HTTP/0.9, which doesn't send +- * headers! Have to dink things just to make sure the error message +- * comes through... +- */ +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00568) +- "client sent invalid HTTP/0.9 request: HEAD %s", +- r->uri); +- r->header_only = 0; +- r->status = HTTP_BAD_REQUEST; +- ap_send_error_response(r, 0); +- ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); +- ap_run_log_transaction(r); +- apr_brigade_destroy(tmp_bb); +- goto traceout; +- } +- } + + apr_brigade_destroy(tmp_bb); + +@@ -1071,7 +1354,7 @@ + * a Host: header, and the server MUST respond with 400 if it doesn't. + */ + access_status = HTTP_BAD_REQUEST; +- ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00569) ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00569) + "client sent HTTP/1.1 request without hostname " + "(see RFC2616 section 14.23): %s", r->uri); + } +diff -uap httpd-2.4.6/server/util.c.cve8743 httpd-2.4.6/server/util.c +--- httpd-2.4.6/server/util.c.cve8743 ++++ httpd-2.4.6/server/util.c +@@ -79,7 +79,7 @@ + * char in here and get it to work, because if char is signed then it + * will first be sign extended. + */ +-#define TEST_CHAR(c, f) (test_char_table[(unsigned)(c)] & (f)) ++#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f)) + + /* Win32/NetWare/OS2 need to check for both forward and back slashes + * in ap_getparents() and ap_escape_url. +@@ -1449,6 +1449,37 @@ + return find_list_item(p, line, tok, AP_ETAG_WEAK); + } + ++/* Scan a string for HTTP VCHAR/obs-text characters including HT and SP ++ * (as used in header values, for example, in RFC 7230 section 3.2) ++ * returning the pointer to the first non-HT ASCII ctrl character. ++ */ ++AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr) ++{ ++ for ( ; !TEST_CHAR(*ptr, T_HTTP_CTRLS); ++ptr) ; ++ ++ return ptr; ++} ++ ++/* Scan a string for HTTP token characters, returning the pointer to ++ * the first non-token character. ++ */ ++AP_DECLARE(const char *) ap_scan_http_token(const char *ptr) ++{ ++ for ( ; !TEST_CHAR(*ptr, T_HTTP_TOKEN_STOP); ++ptr) ; ++ ++ return ptr; ++} ++ ++/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+) ++ * and return a pointer to the first ctrl/space character encountered. ++ */ ++AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr) ++{ ++ for ( ; TEST_CHAR(*ptr, T_VCHAR_OBSTEXT); ++ptr) ; ++ ++ return ptr; ++} ++ + /* Retrieve a token, spacing over it and returning a pointer to + * the first non-white byte afterwards. Note that these tokens + * are delimited by semis and commas; and can also be delimited +diff -uap httpd-2.4.6/server/vhost.c.cve8743 httpd-2.4.6/server/vhost.c +--- httpd-2.4.6/server/vhost.c.cve8743 ++++ httpd-2.4.6/server/vhost.c +@@ -685,6 +685,116 @@ + * run-time vhost matching functions + */ + ++static apr_status_t fix_hostname_v6_literal(request_rec *r, char *host) ++{ ++ char *dst; ++ int double_colon = 0; ++ ++ for (dst = host; *dst; dst++) { ++ if (apr_isxdigit(*dst)) { ++ if (apr_isupper(*dst)) { ++ *dst = apr_tolower(*dst); ++ } ++ } ++ else if (*dst == ':') { ++ if (*(dst + 1) == ':') { ++ if (double_colon) ++ return APR_EINVAL; ++ double_colon = 1; ++ } ++ else if (*(dst + 1) == '.') { ++ return APR_EINVAL; ++ } ++ } ++ else if (*dst == '.') { ++ /* For IPv4-mapped IPv6 addresses like ::FFFF:129.144.52.38 */ ++ if (*(dst + 1) == ':' || *(dst + 1) == '.') ++ return APR_EINVAL; ++ } ++ else { ++ return APR_EINVAL; ++ } ++ } ++ return APR_SUCCESS; ++} ++ ++static apr_status_t fix_hostname_non_v6(request_rec *r, char *host) ++{ ++ char *dst; ++ ++ for (dst = host; *dst; dst++) { ++ if (apr_islower(*dst)) { ++ /* leave char unchanged */ ++ } ++ else if (*dst == '.') { ++ if (*(dst + 1) == '.') { ++ return APR_EINVAL; ++ } ++ } ++ else if (apr_isupper(*dst)) { ++ *dst = apr_tolower(*dst); ++ } ++ else if (*dst == '/' || *dst == '\\') { ++ return APR_EINVAL; ++ } ++ } ++ /* strip trailing gubbins */ ++ if (dst > host && dst[-1] == '.') { ++ dst[-1] = '\0'; ++ } ++ return APR_SUCCESS; ++} ++ ++/* ++ * If strict mode ever becomes the default, this should be folded into ++ * fix_hostname_non_v6() ++ */ ++static apr_status_t strict_hostname_check(request_rec *r, char *host) ++{ ++ char *ch; ++ int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0; ++ ++ for (ch = host; *ch; ch++) { ++ if (!apr_isascii(*ch)) { ++ goto bad; ++ } ++ else if (apr_isalpha(*ch) || *ch == '-') { ++ is_dotted_decimal = 0; ++ } ++ else if (ch[0] == '.') { ++ dots++; ++ if (ch[1] == '0' && apr_isdigit(ch[2])) ++ leading_zeroes = 1; ++ } ++ else if (!apr_isdigit(*ch)) { ++ /* also takes care of multiple Host headers by denying commas */ ++ goto bad; ++ } ++ } ++ if (is_dotted_decimal) { ++ if (host[0] == '.' || (host[0] == '0' && apr_isdigit(host[1]))) ++ leading_zeroes = 1; ++ if (leading_zeroes || dots != 3) { ++ /* RFC 3986 7.4 */ ++ goto bad; ++ } ++ } ++ else { ++ /* The top-level domain must start with a letter (RFC 1123 2.1) */ ++ while (ch > host && *ch != '.') ++ ch--; ++ if (ch[0] == '.' && ch[1] != '\0' && !apr_isalpha(ch[1])) ++ goto bad; ++ } ++ return APR_SUCCESS; ++ ++bad: ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02415) ++ "[strict] Invalid host name '%s'%s%.6s", ++ host, *ch ? ", problem near: " : "", ch); ++ return APR_EINVAL; ++} ++ + /* Lowercase and remove any trailing dot and/or :port from the hostname, + * and check that it is sane. + * +@@ -698,79 +808,90 @@ + * Instead we just check for filesystem metacharacters: directory + * separators / and \ and sequences of more than one dot. + */ +-static void fix_hostname(request_rec *r) ++static int fix_hostname(request_rec *r, const char *host_header, ++ unsigned http_conformance) + { ++ const char *src; + char *host, *scope_id; +- char *dst; + apr_port_t port; + apr_status_t rv; + const char *c; ++ int is_v6literal = 0; ++ int strict = (http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + +- /* According to RFC 2616, Host header field CAN be blank. */ +- if (!*r->hostname) { +- return; ++ src = host_header ? host_header : r->hostname; ++ ++ /* According to RFC 2616, Host header field CAN be blank */ ++ if (!*src) { ++ return is_v6literal; + } + + /* apr_parse_addr_port will interpret a bare integer as a port + * which is incorrect in this context. So treat it separately. + */ +- for (c = r->hostname; apr_isdigit(*c); ++c); +- if (!*c) { /* pure integer */ +- return; ++ for (c = src; apr_isdigit(*c); ++c); ++ if (!*c) { ++ /* pure integer */ ++ if (strict) { ++ /* RFC 3986 7.4 */ ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02416) ++ "[strict] purely numeric host names not allowed: %s", ++ src); ++ goto bad_nolog; ++ } ++ r->hostname = src; ++ return is_v6literal; ++ } ++ ++ if (host_header) { ++ rv = apr_parse_addr_port(&host, &scope_id, &port, src, r->pool); ++ if (rv != APR_SUCCESS || scope_id) ++ goto bad; ++ if (port) { ++ /* Don't throw the Host: header's port number away: ++ save it in parsed_uri -- ap_get_server_port() needs it! */ ++ /* @@@ XXX there should be a better way to pass the port. ++ * Like r->hostname, there should be a r->portno ++ */ ++ r->parsed_uri.port = port; ++ r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); ++ } ++ if (host_header[0] == '[') ++ is_v6literal = 1; ++ } ++ else { ++ /* ++ * Already parsed, surrounding [ ] (if IPv6 literal) and :port have ++ * already been removed. ++ */ ++ host = apr_pstrdup(r->pool, r->hostname); ++ if (ap_strchr(host, ':') != NULL) ++ is_v6literal = 1; + } + +- rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool); +- if (rv != APR_SUCCESS || scope_id) { +- goto bad; ++ if (is_v6literal) { ++ rv = fix_hostname_v6_literal(r, host); + } +- +- if (port) { +- /* Don't throw the Host: header's port number away: +- save it in parsed_uri -- ap_get_server_port() needs it! */ +- /* @@@ XXX there should be a better way to pass the port. +- * Like r->hostname, there should be a r->portno +- */ +- r->parsed_uri.port = port; +- r->parsed_uri.port_str = apr_itoa(r->pool, (int)port); ++ else { ++ rv = fix_hostname_non_v6(r, host); ++ if (strict && rv == APR_SUCCESS) ++ rv = strict_hostname_check(r, host); + } ++ if (rv != APR_SUCCESS) ++ goto bad; + +- /* if the hostname is an IPv6 numeric address string, it was validated +- * already; otherwise, further validation is needed +- */ +- if (r->hostname[0] != '[') { +- for (dst = host; *dst; dst++) { +- if (apr_islower(*dst)) { +- /* leave char unchanged */ +- } +- else if (*dst == '.') { +- if (*(dst + 1) == '.') { +- goto bad; +- } +- } +- else if (apr_isupper(*dst)) { +- *dst = apr_tolower(*dst); +- } +- else if (*dst == '/' || *dst == '\\') { +- goto bad; +- } +- } +- /* strip trailing gubbins */ +- if (dst > host && dst[-1] == '.') { +- dst[-1] = '\0'; +- } +- } + r->hostname = host; +- return; ++ return is_v6literal; + + bad: +- r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550) + "Client sent malformed Host header: %s", +- r->hostname); +- return; ++ src); ++bad_nolog: ++ r->status = HTTP_BAD_REQUEST; ++ return is_v6literal; + } + +- + /* return 1 if host matches ServerName or ServerAliases */ + static int matches_aliases(server_rec *s, const char *host) + { +@@ -980,15 +1101,76 @@ + } + } + ++static APR_INLINE const char *construct_host_header(request_rec *r, ++ int is_v6literal) ++{ ++ struct iovec iov[5]; ++ apr_size_t nvec = 0; ++ /* ++ * We cannot use ap_get_server_name/port here, because we must ++ * ignore UseCanonicalName/Port. ++ */ ++ if (is_v6literal) { ++ iov[nvec].iov_base = "["; ++ iov[nvec].iov_len = 1; ++ nvec++; ++ } ++ iov[nvec].iov_base = (void *)r->hostname; ++ iov[nvec].iov_len = strlen(r->hostname); ++ nvec++; ++ if (is_v6literal) { ++ iov[nvec].iov_base = "]"; ++ iov[nvec].iov_len = 1; ++ nvec++; ++ } ++ if (r->parsed_uri.port_str) { ++ iov[nvec].iov_base = ":"; ++ iov[nvec].iov_len = 1; ++ nvec++; ++ iov[nvec].iov_base = r->parsed_uri.port_str; ++ iov[nvec].iov_len = strlen(r->parsed_uri.port_str); ++ nvec++; ++ } ++ return apr_pstrcatv(r->pool, iov, nvec, NULL); ++} + + AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r) + { +- /* must set this for HTTP/1.1 support */ +- if (r->hostname || (r->hostname = apr_table_get(r->headers_in, "Host"))) { +- fix_hostname(r); +- if (r->status != HTTP_OK) +- return; ++ core_server_config *conf = ap_get_core_module_config(r->server->module_config); ++ const char *host_header = apr_table_get(r->headers_in, "Host"); ++ int is_v6literal = 0; ++ int have_hostname_from_url = 0; ++ ++ if (r->hostname) { ++ /* ++ * If there was a host part in the Request-URI, ignore the 'Host' ++ * header. ++ */ ++ have_hostname_from_url = 1; ++ is_v6literal = fix_hostname(r, NULL, conf->http_conformance); ++ } ++ else if (host_header != NULL) { ++ is_v6literal = fix_hostname(r, host_header, conf->http_conformance); ++ } ++ if (r->status != HTTP_OK) ++ return; ++ ++ if (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE) { ++ /* ++ * If we have both hostname from an absoluteURI and a Host header, ++ * we must ignore the Host header (RFC 2616 5.2). ++ * To enforce this, we reset the Host header to the value from the ++ * request line. ++ */ ++ if (have_hostname_from_url && host_header != NULL) { ++ const char *repl = construct_host_header(r, is_v6literal); ++ apr_table_set(r->headers_in, "Host", repl); ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02417) ++ "Replacing host header '%s' with host '%s' given " ++ "in the request uri", host_header, repl); ++ } + } ++ + /* check if we tucked away a name_chain */ + if (r->connection->vhost_lookup_data) { + if (r->hostname) diff --git a/SOURCES/httpd-2.4.6-r1570327.patch b/SOURCES/httpd-2.4.6-r1570327.patch new file mode 100644 index 0000000..35cdce8 --- /dev/null +++ b/SOURCES/httpd-2.4.6-r1570327.patch @@ -0,0 +1,20 @@ +# ./pullrev.sh 1570327 +http://svn.apache.org/viewvc?view=revision&revision=1570327 + +https://bugzilla.redhat.com/show_bug.cgi?id=1327624 + +--- httpd-2.4.6/server/mpm_unix.c ++++ httpd-2.4.6/server/mpm_unix.c +@@ -742,7 +742,12 @@ + * readers stranded (a number of them could be tied up for + * a while serving time-consuming requests) + */ ++ /* Recall: we only worry about IDLE child processes here */ + for (i = 0; i < num && rv == APR_SUCCESS; i++) { ++ if (ap_scoreboard_image->servers[i][0].status != SERVER_READY || ++ ap_scoreboard_image->servers[i][0].pid == 0) { ++ continue; ++ } + rv = dummy_connection(pod); + } + } diff --git a/SOURCES/httpd-2.4.6-r1587053.patch b/SOURCES/httpd-2.4.6-r1587053.patch new file mode 100644 index 0000000..5bee0b1 --- /dev/null +++ b/SOURCES/httpd-2.4.6-r1587053.patch @@ -0,0 +1,92 @@ +diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c +index 525109a..eb34eee 100644 +--- a/modules/proxy/mod_proxy_wstunnel.c ++++ b/modules/proxy/mod_proxy_wstunnel.c +@@ -103,10 +103,12 @@ static int proxy_wstunnel_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o, + rv = ap_get_brigade(c_i->input_filters, bb, AP_MODE_READBYTES, + APR_NONBLOCK_READ, AP_IOBUFSIZE); + if (rv == APR_SUCCESS) { +- if (c_o->aborted) ++ if (c_o->aborted) { + return APR_EPIPE; +- if (APR_BRIGADE_EMPTY(bb)) ++ } ++ if (APR_BRIGADE_EMPTY(bb)){ + break; ++ } + #ifdef DEBUGGING + len = -1; + apr_brigade_length(bb, 0, &len); +@@ -178,7 +180,6 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, + conn_rec *c = r->connection; + apr_socket_t *sock = conn->sock; + conn_rec *backconn = conn->connection; +- int client_error = 0; + char *buf; + apr_bucket_brigade *header_brigade; + apr_bucket *e; +@@ -224,7 +225,7 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, + + pollfd.p = p; + pollfd.desc_type = APR_POLL_SOCKET; +- pollfd.reqevents = APR_POLLIN; ++ pollfd.reqevents = APR_POLLIN | APR_POLLHUP; + pollfd.desc.s = sock; + pollfd.client_data = NULL; + apr_pollset_add(pollset, &pollfd); +@@ -237,6 +238,9 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, + r->proto_output_filters = c->output_filters; + r->input_filters = c->input_filters; + r->proto_input_filters = c->input_filters; ++ /* This handler should take care of the entire connection; make it so that ++ * nothing else is attempted on the connection after returning. */ ++ c->keepalive = AP_CONN_CLOSE; + + remove_reqtimeout(r->input_filters); + +@@ -257,26 +261,28 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, + + if (cur->desc.s == sock) { + pollevent = cur->rtnevents; +- if (pollevent & APR_POLLIN) { ++ if (pollevent & (APR_POLLIN | APR_POLLHUP)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02446) + "sock was readable"); + rv = proxy_wstunnel_transfer(r, backconn, c, bb, "sock"); + } +- else if ((pollevent & APR_POLLERR) +- || (pollevent & APR_POLLHUP)) { ++ else if (pollevent & APR_POLLERR) { + rv = APR_EPIPE; + ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, APLOGNO(02447) +- "err/hup on backconn"); ++ "err on backconn"); + } +- if (rv != APR_SUCCESS) +- client_error = 1; + } + else if (cur->desc.s == client_socket) { + pollevent = cur->rtnevents; +- if (pollevent & APR_POLLIN) { ++ if (pollevent & (APR_POLLIN | APR_POLLHUP)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02448) + "client was readable"); + rv = proxy_wstunnel_transfer(r, c, backconn, bb, "client"); ++ } else if (pollevent & APR_POLLERR) { ++ rv = APR_EPIPE; ++ c->aborted = 1; ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(02607) ++ "error on client conn"); + } + } + else { +@@ -294,9 +300,6 @@ static int ap_proxy_wstunnel_request(apr_pool_t *p, request_rec *r, + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "finished with poll() - cleaning up"); + +- if (client_error) { +- return HTTP_INTERNAL_SERVER_ERROR; +- } + return OK; + } + diff --git a/SOURCES/httpd-2.4.6-r1631119.patch b/SOURCES/httpd-2.4.6-r1631119.patch new file mode 100644 index 0000000..f0f48e4 --- /dev/null +++ b/SOURCES/httpd-2.4.6-r1631119.patch @@ -0,0 +1,16 @@ +# ./pullrev.sh 1631119 +http://svn.apache.org/viewvc?view=revision&revision=1631119 + +https://bugzilla.redhat.com/show_bug.cgi?id=1415257 + +--- httpd-2.4.6/modules/ldap/util_ldap.c ++++ httpd-2.4.6/modules/ldap/util_ldap.c +@@ -1824,7 +1824,7 @@ + * combination, which might be reused unintentionally next time this + * connection is used from the connection pool. + */ +- ldc->must_rebind = 0; ++ ldc->must_rebind = 1; + ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, "LDC %pp used for authn, must be rebound", ldc); + } + diff --git a/SOURCES/welcome.conf b/SOURCES/welcome.conf index c1b6c11..5d1e452 100644 --- a/SOURCES/welcome.conf +++ b/SOURCES/welcome.conf @@ -16,7 +16,3 @@ Alias /.noindex.html /usr/share/httpd/noindex/index.html -Alias /noindex/css/bootstrap.min.css /usr/share/httpd/noindex/css/bootstrap.min.css -Alias /noindex/css/open-sans.css /usr/share/httpd/noindex/css/open-sans.css -Alias /images/apache_pb.gif /usr/share/httpd/noindex/images/apache_pb.gif -Alias /images/poweredby.png /usr/share/httpd/noindex/images/poweredby.png diff --git a/SPECS/httpd.spec b/SPECS/httpd.spec index 7ce38f6..4280492 100644 --- a/SPECS/httpd.spec +++ b/SPECS/httpd.spec @@ -4,7 +4,7 @@ %define mmn 20120211 %define oldmmnisa %{mmn}-%{__isa_name}-%{__isa_bits} %define mmnisa %{mmn}%{__isa_name}%{__isa_bits} -%define vstring CentOS +%define vstring %(source /etc/os-release; echo ${REDHAT_SUPPORT_PRODUCT}) # Drop automatic provides for module DSOs %{?filter_setup: @@ -15,10 +15,10 @@ Summary: Apache HTTP Server Name: httpd Version: 2.4.6 -Release: 45%{?dist} +Release: 45%{?dist}.4 URL: http://httpd.apache.org/ Source0: http://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2 -Source1: centos-noindex.tar.gz +Source1: index.html Source2: httpd.logrotate Source3: httpd.sysconf Source4: httpd-ssl-pass-dialog @@ -124,6 +124,9 @@ Patch103: httpd-2.4.6-dhparams-free.patch Patch104: httpd-2.4.6-r1651658.patch Patch105: httpd-2.4.6-r1560093.patch Patch106: httpd-2.4.6-r1748212.patch +Patch107: httpd-2.4.6-r1570327.patch +Patch108: httpd-2.4.6-r1631119.patch +Patch109: httpd-2.4.6-r1587053.patch # Security fixes Patch200: httpd-2.4.6-CVE-2013-6438.patch Patch201: httpd-2.4.6-CVE-2014-0098.patch @@ -137,6 +140,9 @@ Patch208: httpd-2.4.6-CVE-2014-3581.patch Patch209: httpd-2.4.6-CVE-2015-3185.patch Patch210: httpd-2.4.6-CVE-2015-3183.patch Patch211: httpd-2.4.6-CVE-2016-5387.patch +Patch212: httpd-2.4.6-CVE-2016-8743.patch +Patch213: httpd-2.4.6-CVE-2016-0736.patch +Patch214: httpd-2.4.6-CVE-2016-2161.patch License: ASL 2.0 Group: System Environment/Daemons BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root @@ -323,6 +329,9 @@ rm modules/ssl/ssl_engine_dh.c %patch104 -p1 -b .r1651658 %patch105 -p1 -b .r1560093 %patch106 -p1 -b .r1748212 +%patch107 -p1 -b .r1570327 +%patch108 -p1 -b .r1631119 +%patch109 -p1 -b .r1587053 %patch200 -p1 -b .cve6438 %patch201 -p1 -b .cve0098 @@ -336,6 +345,9 @@ rm modules/ssl/ssl_engine_dh.c %patch209 -p1 -b .cve3185 %patch210 -p1 -b .cve3183 %patch211 -p1 -b .cve5387 +%patch212 -p1 -b .cve8743 +%patch213 -p1 -b .cve0736 +%patch214 -p1 -b .cve2161 # Patch in the vendor string and the release string sed -i '/^#define PLATFORM/s/Unix/%{vstring}/' os/unix/os.h @@ -489,10 +501,8 @@ EOF # Handle contentdir mkdir $RPM_BUILD_ROOT%{contentdir}/noindex -tar xzf $RPM_SOURCE_DIR/centos-noindex.tar.gz \ - -C $RPM_BUILD_ROOT%{contentdir}/noindex/ \ - --strip-components=1 - +install -m 644 -p $RPM_SOURCE_DIR/index.html \ + $RPM_BUILD_ROOT%{contentdir}/noindex/index.html rm -rf %{contentdir}/htdocs # remove manual sources @@ -515,7 +525,7 @@ rm -v $RPM_BUILD_ROOT%{docroot}/html/*.html \ $RPM_BUILD_ROOT%{docroot}/cgi-bin/* # Symlink for the powered-by-$DISTRO image: -ln -s ../noindex/images/poweredby.png \ +ln -s ../../pixmaps/poweredby.png \ $RPM_BUILD_ROOT%{contentdir}/icons/poweredby.png # symlinks for /etc/httpd @@ -701,7 +711,7 @@ rm -rf $RPM_BUILD_ROOT %{contentdir}/error/README %{contentdir}/error/*.var %{contentdir}/error/include/*.html -%{contentdir}/noindex/* +%{contentdir}/noindex/index.html %dir %{docroot} %dir %{docroot}/cgi-bin @@ -767,11 +777,20 @@ rm -rf $RPM_BUILD_ROOT %{_sysconfdir}/rpm/macros.httpd %changelog -* Thu Nov 03 2016 CentOS Sources - 2.4.6-45.el7.centos -- Remove index.html, add centos-noindex.tar.gz -- change vstring -- change symlink for poweredby.png -- update welcome.conf with proper aliases +* Wed Mar 08 2017 Luboš Uhliarik - 2.4.6-45.4 +- Resolves: #1396197 - Backport: mod_proxy_wstunnel - AH02447: err/hup + on backconn + +* Tue Feb 14 2017 Joe Orton - 2.4.6-45.3 +- prefork: fix delay completing graceful restart (#1327624) +- mod_ldap: fix authz regression, failing to rebind (#1415257) + +* Tue Feb 14 2017 Joe Orton - 2.4.6-45.2 +- updated patch for CVE-2016-8743 + +* Mon Jan 30 2017 Luboš Uhliarik - 2.4.6-45.1 +- Resolves: #1412975 - CVE-2016-0736 CVE-2016-2161 CVE-2016-8743 httpd: various + flaws * Wed Aug 03 2016 Luboš Uhliarik - 2.4.6-45 - RFE: run mod_rewrite external mapping program as non-root (#1316900)