Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP2:Update
apache2.8508
apache2-CVE-2016-8743.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File apache2-CVE-2016-8743.patch of Package apache2.8508
Index: httpd-2.4.16/include/http_core.h =================================================================== --- httpd-2.4.16.orig/include/http_core.h 2017-02-28 09:06:40.474307264 +0100 +++ httpd-2.4.16/include/http_core.h 2017-02-28 09:27:41.508280222 +0100 @@ -690,6 +690,22 @@ typedef struct { #define AP_HTTP_EXPECT_STRICT_ENABLE 1 #define AP_HTTP_EXPECT_STRICT_DISABLE 2 int http_expect_strict; + +#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 */ Index: httpd-2.4.16/include/http_protocol.h =================================================================== --- httpd-2.4.16.orig/include/http_protocol.h 2015-05-29 22:07:15.000000000 +0200 +++ httpd-2.4.16/include/http_protocol.h 2017-02-28 09:06:40.474307264 +0100 @@ -582,17 +582,22 @@ AP_DECLARE(int) ap_get_basic_auth_pw(req */ 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 @@ AP_DECLARE(int) ap_getline(char *s, int * @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 @@ AP_DECLARE(int) ap_getline(char *s, int #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 @@ AP_DECLARE(apr_status_t) ap_rgetline(cha /** @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); /** Index: httpd-2.4.16/include/httpd.h =================================================================== --- httpd-2.4.16.orig/include/httpd.h 2017-02-28 09:06:40.442306707 +0100 +++ httpd-2.4.16/include/httpd.h 2017-02-28 09:06:40.474307264 +0100 @@ -1585,6 +1585,28 @@ AP_DECLARE(int) ap_find_last_token(apr_p */ AP_DECLARE(int) ap_is_url(const char *u); +/* 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 string * @param url The string to unescape Index: httpd-2.4.16/modules/http/http_filters.c =================================================================== --- httpd-2.4.16.orig/modules/http/http_filters.c 2017-02-28 09:06:40.450306846 +0100 +++ httpd-2.4.16/modules/http/http_filters.c 2017-02-28 09:06:40.474307264 +0100 @@ -126,14 +126,15 @@ static apr_status_t bail_out_on_error(ht /** * 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 @@ static apr_status_t parse_chunk_size(htt 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 @@ static apr_status_t parse_chunk_size(htt } else { /* - * LF expected. + * CRLF expected. */ return APR_EINVAL; } @@ -180,6 +187,12 @@ static apr_status_t parse_chunk_size(htt } 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 @@ static apr_status_t parse_chunk_size(htt } 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 @@ apr_status_t ap_http_filter(ap_filter_t 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_status_t ap_http_filter(ap_filter_t 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 @@ apr_status_t ap_http_filter(ap_filter_t 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,83 @@ apr_status_t ap_http_filter(ap_filter_t 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(void *arg, const char *name, const char *val) +{ + struct check_header_ctx *ctx = arg; + const char *test; + + 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) { + test = ap_scan_http_token(name); + } + else { + test = ap_scan_vchar_obstext(name); + } + if (*test) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02429) + "Response header name '%s' contains invalid " + "characters, aborting request", + name); + return 0; + } + + if (ctx->strict) { + test = ap_scan_http_field_content(val); + } + else { + /* Simply terminate scanning on a CTL char, allowing whitespace */ + test = val; + do { + while (*test == ' ' || *test == '\t') test++; + test = ap_scan_vchar_obstext(test); + } while (*test == ' ' || *test == '\t'); + } + if (*test) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02430) + "Response header '%s' value of '%s' contains invalid " + "characters, aborting request", + name, 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); + if (!apr_table_do(check_header, &ctx, r->headers_out, NULL)) + return 0; /* problem has been logged by check_header() */ + + return 1; +} + 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, @@ -1231,6 +1316,11 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_ r->headers_out); } + if (!check_headers(r)) { + ap_die(HTTP_INTERNAL_SERVER_ERROR, r); + return AP_FILTER_ERROR; + } + /* * Remove the 'Vary' header field if the client can't handle it. * Since this will have nasty effects on HTTP/1.1 caches, force Index: httpd-2.4.16/server/core.c =================================================================== --- httpd-2.4.16.orig/server/core.c 2017-02-28 09:06:40.450306846 +0100 +++ httpd-2.4.16/server/core.c 2017-02-28 09:37:45.554793712 +0100 @@ -503,6 +503,15 @@ static void *merge_core_server_configs(a if (virt->http_expect_strict != AP_HTTP_EXPECT_STRICT_UNSET) conf->http_expect_strict = virt->http_expect_strict; + 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) @@ -3724,6 +3733,57 @@ static const char *set_expect_strict(cmd 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, @@ -4241,6 +4301,12 @@ AP_INIT_FLAG("HttpContentLengthHeadZero" "whether to permit Content-Length of 0 responses to HEAD requests"), AP_INIT_FLAG("HttpExpectStrict", set_expect_strict, NULL, OR_OPTIONS, "whether to return a 417 if a client doesn't send 100-Continue"), +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 } }; Index: httpd-2.4.16/server/gen_test_char.c =================================================================== --- httpd-2.4.16.orig/server/gen_test_char.c 2013-01-28 14:13:18.000000000 +0100 +++ httpd-2.4.16/server/gen_test_char.c 2017-02-28 09:06:40.478307333 +0100 @@ -16,11 +16,11 @@ #ifdef CROSS_COMPILE +#include <ctype.h> #define apr_isalnum(c) (isalnum(((unsigned char)(c)))) #define apr_isalpha(c) (isalpha(((unsigned char)(c)))) #define apr_iscntrl(c) (iscntrl(((unsigned char)(c)))) #define apr_isprint(c) (isprint(((unsigned char)(c)))) -#include <ctype.h> #define APR_HAVE_STDIO_H 1 #define APR_HAVE_STRING_H 1 @@ -52,11 +52,13 @@ #define T_ESCAPE_LOGITEM (0x10) #define T_ESCAPE_FORENSIC (0x20) #define T_ESCAPE_URLENCODED (0x40) +#define T_HTTP_CTRLS (0x80) +#define T_VCHAR_OBSTEXT (0x100) int main(int argc, char *argv[]) { unsigned c; - unsigned char flags; + unsigned short flags; printf("/* this file is automatically generated by gen_test_char, " "do not edit */\n" @@ -67,19 +69,23 @@ int main(int argc, char *argv[]) "#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 @@ int main(int argc, char *argv[]) flags |= T_ESCAPE_PATH_SEGMENT; } - if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=/~", c)) { + if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:;@&=/~", c)) { flags |= T_OS_ESCAPE_PATH; } @@ -115,11 +121,32 @@ int main(int argc, char *argv[]) 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 @@ int main(int argc, char *argv[]) flags |= T_ESCAPE_FORENSIC; } - printf("%u%c", flags, (c < 255) ? ',' : ' '); + printf("0x%03x%c", flags, (c < 255) ? ',' : ' '); } printf("\n};\n"); Index: httpd-2.4.16/server/protocol.c =================================================================== --- httpd-2.4.16.orig/server/protocol.c 2017-02-28 09:06:40.450306846 +0100 +++ httpd-2.4.16/server/protocol.c 2017-02-28 11:20:06.522132707 +0100 @@ -188,6 +188,10 @@ AP_DECLARE(apr_time_t) ap_rationalize_mt /* Get a line of protocol input, including any continuation lines * caused by MIME folding (or broken clients) if fold != 0, and place it * in the buffer s, of size n bytes, without the ending newline. + * + * Pulls from r->proto_input_filters instead of r->input_filters for + * stricter protocol adherence and better input filter behavior during + * chunked trailer processing (for http). * * If s is NULL, ap_rgetline_core will allocate necessary memory from r->pool. * @@ -197,7 +201,7 @@ AP_DECLARE(apr_time_t) ap_rationalize_mt * 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_time_t) ap_rationalize_mt */ 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 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor 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 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor } } + 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 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor 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 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor */ if (do_alloc) { tmp = NULL; - } else { + } + else { /* We're null terminated. */ tmp = last_char; } @@ -461,7 +477,7 @@ AP_DECLARE(apr_status_t) ap_rgetline(cha } #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 @@ AP_DECLARE(int) ap_getline(char *s, int 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,17 +565,31 @@ AP_CORE_DECLARE(void) ap_parse_uri(reque } } +/* 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; +} + static int read_request_line(request_rec *r, apr_bucket_brigade *bb) { - const char *ll; - const char *uri; - const char *pro; + 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; - unsigned int major = 1, minor = 0; /* Assume HTTP/1.0 if non-"HTTP" protocol */ - char http[5]; apr_size_t len; int num_blank_lines = 0; int max_blank_lines = r->server->limit_req_fields; + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); if (max_blank_lines <= 0) { max_blank_lines = DEFAULT_LIMIT_REQUEST_FIELDS; @@ -588,7 +618,7 @@ static int read_request_line(request_rec */ 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(); @@ -598,7 +628,7 @@ static int read_request_line(request_rec * happen if it exceeds the configured limit for a request-line. */ if (APR_STATUS_IS_ENOSPC(rv)) { - r->status = HTTP_REQUEST_URI_TOO_LARGE; + r->status = HTTP_REQUEST_URI_TOO_LARGE; } else if (APR_STATUS_IS_TIMEUP(rv)) { r->status = HTTP_REQUEST_TIME_OUT; @@ -619,12 +649,159 @@ static int read_request_line(request_rec } 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) + ; + } + + /* Scan the method up to the next whitespace, ensure it contains only + * valid http-token characters, otherwise mark in error + */ + if (strict) { + ll = (char*) ap_scan_http_token(r->method); + } + else { + ll = (char*) ap_scan_vchar_obstext(r->method); + } + + if (((ll == r->method) || (*ll && !apr_isspace(*ll))) + && deferred_error == rrl_none) { + deferred_error = rrl_badmethod; + ll = strpbrk(ll, "\t\n\v\f\r "); + } + + /* Verify method terminated with a single SP, or mark as specific error */ + if (!ll) { + if (deferred_error == rrl_none) + deferred_error = rrl_missinguri; + r->protocol = uri = ""; + len = 0; + goto rrl_done; + } + else if (strict && ll[0] && apr_isspace(ll[1]) + && deferred_error == rrl_none) { + deferred_error = rrl_excesswhitespace; + } + + /* Advance uri pointer over leading whitespace, NUL terminate the method + * If non-SP whitespace is encountered, mark as specific error + */ + for (uri = ll; apr_isspace(*uri); ++uri) + if (*uri != ' ' && deferred_error == rrl_none) + deferred_error = rrl_badwhitespace; + *ll = '\0'; + + if (!*uri && deferred_error == rrl_none) + deferred_error = rrl_missinguri; + + /* Scan the URI up to the next whitespace, ensure it contains no raw + * control characters, otherwise mark in error + */ + ll = (char*) ap_scan_vchar_obstext(uri); + if (ll == uri || (*ll && !apr_isspace(*ll))) { + deferred_error = rrl_baduri; + ll = strpbrk(ll, "\t\n\v\f\r "); + } + + /* Verify URI terminated with a single SP, or mark as specific error */ + if (!ll) { + r->protocol = ""; + len = 0; + goto rrl_done; + } + else if (strict && ll[0] && apr_isspace(ll[1]) + && deferred_error == rrl_none) { + deferred_error = rrl_excesswhitespace; + } + + /* Advance protocol pointer over leading whitespace, NUL terminate the uri + * If non-SP whitespace is encountered, mark as specific error + */ + for (r->protocol = ll; apr_isspace(*r->protocol); ++r->protocol) + if (*r->protocol != ' ' && deferred_error == rrl_none) + deferred_error = rrl_badwhitespace; + *ll = '\0'; + + /* Scan the protocol up to the next whitespace, validation comes later */ + if (!(ll = (char*) ap_scan_vchar_obstext(r->protocol))) { + len = strlen(r->protocol); + goto rrl_done; + } + len = ll - r->protocol; + + /* Advance over trailing whitespace, if found mark in error, + * determine if trailing text is found, unconditionally mark in error, + * finally NUL terminate the protocol string + */ + if (*ll && !apr_isspace(*ll)) { + deferred_error = rrl_badprotocol; + } + else if (strict && *ll) { + deferred_error = rrl_excesswhitespace; + } + else { + for ( ; apr_isspace(*ll); ++ll) + if (*ll != ' ' && deferred_error == rrl_none) + deferred_error = rrl_badwhitespace; + if (*ll && deferred_error == rrl_none) + deferred_error = rrl_trailingtext; + } + *((char *)r->protocol + len) = '\0'; + +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); - uri = ap_getword_white(r->pool, &ll); + if (len == 8 + && r->protocol[0] == 'H' && r->protocol[1] == 'T' + && r->protocol[2] == 'T' && r->protocol[3] == 'P' + && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) + && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) + && r->protocol[5] != '0') { + r->assbackwards = 0; + r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); + } + else if (len == 8 + && (r->protocol[0] == 'H' || r->protocol[0] == 'h') + && (r->protocol[1] == 'T' || r->protocol[1] == 't') + && (r->protocol[2] == 'T' || r->protocol[2] == 't') + && (r->protocol[3] == 'P' || r->protocol[3] == 'p') + && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) + && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) + && r->protocol[5] != '0') { + r->assbackwards = 0; + r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); + if (strict && deferred_error == rrl_none) + deferred_error = rrl_badprotocol; + else + memcpy((char*)r->protocol, "HTTP", 4); + } + else if (r->protocol[0]) { + r->assbackwards = 0; + r->proto_num = HTTP_VERSION(1,0); + /* Defer setting the r->protocol string till error msg is composed */ + if (strict && deferred_error == rrl_none) + deferred_error = rrl_badprotocol; + else + r->protocol = "HTTP/1.0"; + } + else { + r->assbackwards = 1; + r->protocol = "HTTP/0.9"; + r->proto_num = HTTP_VERSION(0, 9); + } - /* Provide quick information about the request method as soon as known */ + /* 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') { @@ -633,32 +810,110 @@ static int read_request_line(request_rec ap_parse_uri(r, uri); - if (ll[0]) { - r->assbackwards = 0; - pro = ll; - len = strlen(ll); - } else { - r->assbackwards = 1; - pro = "HTTP/0.9"; - len = 8; + /* With the request understood, we can consider HTTP/0.9 specific errors */ + if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) { + if (conf->http09_enable == AP_HTTP09_DISABLE) + deferred_error = rrl_reject09; + else if (strict && (r->method_number != M_GET || r->header_only)) + deferred_error = rrl_badmethod09; } - r->protocol = apr_pstrmemdup(r->pool, pro, len); - /* 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); + /* 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 = "HTTP/1.0"; + } + return 0; } static int table_do_fn_check_lengths(void *r_, const char *key, @@ -670,26 +925,13 @@ static int table_do_fn_check_lengths(voi r->status = HTTP_BAD_REQUEST; apr_table_setn(r->notes, "error-notes", - apr_pstrcat(r->pool, "Size of a request header field " - "after merging exceeds server limit.<br />" - "\n<pre>\n", - ap_escape_html(r->pool, key), - "</pre>\n", NULL)); - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request header " - "exceeds LimitRequestFieldSize after merging: %s", key); + "Size of a request header field exceeds server limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00560) "Request " + "header exceeds LimitRequestFieldSize after merging: %.*s", + field_name_len(key), key); return 0; } -/* get the length of the field name for logging, but no more than 80 bytes */ -#define LOG_NAME_MAX_LEN 80 -static int field_name_len(const char *field) -{ - const char *end = ap_strchr_c(field, ':'); - if (end == NULL || end - field > LOG_NAME_MAX_LEN) - return LOG_NAME_MAX_LEN; - return end - field; -} - AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb) { char *last_field = NULL; @@ -700,6 +942,8 @@ AP_DECLARE(void) ap_get_mime_headers_cor 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 +951,10 @@ AP_DECLARE(void) ap_get_mime_headers_cor */ 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,156 +971,218 @@ AP_DECLARE(void) ap_get_mime_headers_cor * exceeds the configured limit for a field size. */ if (rv == APR_ENOSPC) { - const char *field_escaped; - if (field && len) { - /* ensure ap_escape_html will terminate correctly */ - field[len - 1] = '\0'; - field_escaped = ap_escape_html(r->pool, field); - } - else { - field_escaped = field = ""; - } - apr_table_setn(r->notes, "error-notes", - apr_psprintf(r->pool, - "Size of a request header field " - "exceeds server limit.<br />\n" - "<pre>\n%.*s\n</pre>\n", - field_name_len(field_escaped), - field_escaped)); + "Size of a request header field " + "exceeds server limit."); ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00561) "Request header exceeds LimitRequestFieldSize%s" "%.*s", - *field ? ": " : "", - field_name_len(field), field); + (field && *field) ? ": " : "", + (field) ? field_name_len(field) : 0, + (field) ? field : ""); } return; } - if (last_field != NULL) { - if ((len > 0) && ((*field == '\t') || *field == ' ')) { - /* This line is a continuation of the preceding line(s), - * so append it to the line that we've set aside. - * Note: this uses a power-of-two allocator to avoid - * doing O(n) allocs and using O(n^2) space for - * continuations that span many many lines. - */ - apr_size_t fold_len = last_len + len + 1; /* trailing null */ + /* For all header values, and all obs-fold lines, the presence of + * additional whitespace is a no-op, so collapse trailing whitespace + * to save buffer allocation and optimize copy operations. + * Do not remove the last single whitespace under any condition. + */ + while (len > 1 && (field[len-1] == '\t' || field[len-1] == ' ')) { + field[--len] = '\0'; + } - if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { - const char *field_escaped; + if (*field == '\t' || *field == ' ') { - r->status = HTTP_BAD_REQUEST; - /* report what we have accumulated so far before the - * overflow (last_field) as the field with the problem - */ - field_escaped = ap_escape_html(r->pool, last_field); - apr_table_setn(r->notes, "error-notes", - apr_psprintf(r->pool, - "Size of a request header field " - "after folding " - "exceeds server limit.<br />\n" - "<pre>\n%.*s\n</pre>\n", - field_name_len(field_escaped), - field_escaped)); - 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; - } + /* Append any newly-read obs-fold line onto the preceding + * last_field line we are processing + */ + apr_size_t fold_len; + + if (last_field == NULL) { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03442) + "Line folding encountered before first" + " header line"); + return; + } + + if (field[1] == '\0') { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03443) + "Empty folded line encountered"); + return; + } + + /* Leading whitespace on an obs-fold line can be + * similarly discarded */ + while (field[1] == '\t' || field[1] == ' ') { + ++field; --len; + } + + /* This line is a continuation of the preceding line(s), + * so append it to the line that we've set aside. + * Note: this uses a power-of-two allocator to avoid + * doing O(n) allocs and using O(n^2) space for + * continuations that span many many lines. + */ + fold_len = last_len + len + 1; /* trailing null */ + + if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) { + r->status = HTTP_BAD_REQUEST; + /* report what we have accumulated so far before the + * overflow (last_field) as the field with the problem + */ + apr_table_setn(r->notes, "error-notes", + "Size of a request header field " + "exceeds server limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562) + "Request header exceeds LimitRequestFieldSize " + "after folding: %.*s", + field_name_len(last_field), last_field); + return; + } + if (fold_len > alloc_len) { + char *fold_buf; + alloc_len += alloc_len; if (fold_len > alloc_len) { - char *fold_buf; - alloc_len += alloc_len; - if (fold_len > alloc_len) { - alloc_len = fold_len; - } - fold_buf = (char *)apr_palloc(r->pool, alloc_len); - memcpy(fold_buf, last_field, last_len); - last_field = fold_buf; + alloc_len = fold_len; } - memcpy(last_field + last_len, field, len +1); /* +1 for nul */ - last_len += len; - folded = 1; - } - else /* not a continuation line */ { + fold_buf = (char *)apr_palloc(r->pool, alloc_len); + memcpy(fold_buf, last_field, last_len); + last_field = fold_buf; + } + memcpy(last_field + last_len, field, len +1); /* +1 for nul */ + /* Replace obs-fold w/ SP per RFC 7230 3.2.4 */ + last_field[last_len] = ' '; + last_len += len; - if (r->server->limit_req_fields + /* We've appended this obs-fold line to last_len, proceed to + * read the next input line + */ + continue; + } + else if (last_field != NULL) { + + /* Process the previous last_field header line with all obs-folded + * segments already concatinated (this is not operating on the + * most recently read input line). + */ + + if (r->server->limit_req_fields && (++fields_read > r->server->limit_req_fields)) { - r->status = HTTP_BAD_REQUEST; - apr_table_setn(r->notes, "error-notes", - "The number of request header fields " - "exceeds this server's limit."); - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563) - "Number of request headers exceeds " - "LimitRequestFields"); - return; - } + r->status = HTTP_BAD_REQUEST; + apr_table_setn(r->notes, "error-notes", + "The number of request header fields " + "exceeds this server's limit."); + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563) + "Number of request headers exceeds " + "LimitRequestFields"); + return; + } - if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ - r->status = HTTP_BAD_REQUEST; /* abort bad request */ - apr_table_setn(r->notes, "error-notes", - apr_psprintf(r->pool, - "Request header field is " - "missing ':' separator.<br />\n" - "<pre>\n%.*s</pre>\n", - (int)LOG_NAME_MAX_LEN, - ap_escape_html(r->pool, - last_field))); - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00564) + if (!strict) + { + /* Not Strict ('Unsafe' mode), using the legacy parser */ + + if (!(value = strchr(last_field, ':'))) { /* Find ':' or */ + r->status = HTTP_BAD_REQUEST; /* abort bad request */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00564) "Request header field is missing ':' " "separator: %.*s", (int)LOG_NAME_MAX_LEN, last_field); return; } - tmp_field = value - 1; /* last character of field-name */ + /* last character of field-name */ + tmp_field = value - (value > last_field ? 1 : 0); *value++ = '\0'; /* NUL-terminate at colon */ + if (strpbrk(last_field, "\t\n\v\f\r ")) { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03452) + "Request header field name presented" + " invalid whitespace"); + return; + } + while (*value == ' ' || *value == '\t') { - ++value; /* Skip to start of value */ + ++value; /* Skip to start of value */ } - /* Strip LWS after field-name: */ - while (tmp_field > last_field - && (*tmp_field == ' ' || *tmp_field == '\t')) { - *tmp_field-- = '\0'; + if (strpbrk(value, "\n\v\f\r")) { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03451) + "Request header field value presented" + " bad whitespace"); + return; } - /* Strip LWS after field-value: */ - tmp_field = last_field + last_len - 1; - while (tmp_field > value - && (*tmp_field == ' ' || *tmp_field == '\t')) { - *tmp_field-- = '\0'; + if (tmp_field == last_field) { + r->status = HTTP_BAD_REQUEST; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, 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; + } + + *value++ = '\0'; /* NUL-terminate last_field name at ':' */ - apr_table_addn(r->headers_in, last_field, value); + while (*value == ' ' || *value == '\t') { + ++value; /* Skip LWS of 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 + /* Find invalid, non-HT ctrl char, or the trailing NULL */ + tmp_field = (char *)ap_scan_http_field_content(value); + + /* 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 @@ -902,7 +1207,7 @@ request_rec *ap_read_request(conn_rec *c 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; @@ -961,16 +1266,19 @@ request_rec *ap_read_request(conn_rec *c /* 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"); } access_status = r->status; r->status = HTTP_OK; @@ -980,19 +1288,18 @@ request_rec *ap_read_request(conn_rec *c r = NULL; apr_brigade_destroy(tmp_bb); goto traceout; - } - else if (r->status == HTTP_REQUEST_TIME_OUT) { + case HTTP_REQUEST_TIME_OUT: ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r); if (!r->connection->keepalives) { 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 @@ -1011,7 +1318,7 @@ request_rec *ap_read_request(conn_rec *c 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); @@ -1030,7 +1337,7 @@ request_rec *ap_read_request(conn_rec *c */ if (!(strcasecmp(tenc, "chunked") == 0 /* fast path */ || ap_find_last_token(r->pool, tenc, "chunked"))) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02539) + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02539) "client sent unknown Transfer-Encoding " "(%s): %s", tenc, r->uri); r->status = HTTP_BAD_REQUEST; @@ -1051,25 +1358,6 @@ request_rec *ap_read_request(conn_rec *c 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); @@ -1077,6 +1365,7 @@ request_rec *ap_read_request(conn_rec *c * now read. may update status. */ ap_update_vhost_from_headers(r); + access_status = r->status; /* Toggle to the Host:-based vhost's timeout mode to fetch the * request body and send the response body, if needed. @@ -1100,7 +1389,7 @@ request_rec *ap_read_request(conn_rec *c * 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); } Index: httpd-2.4.16/server/util.c =================================================================== --- httpd-2.4.16.orig/server/util.c 2015-05-29 22:07:15.000000000 +0200 +++ httpd-2.4.16/server/util.c 2017-02-28 09:06:40.478307333 +0100 @@ -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. @@ -1451,6 +1451,37 @@ AP_DECLARE(int) ap_find_etag_weak(apr_po 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 Index: httpd-2.4.16/server/vhost.c =================================================================== --- httpd-2.4.16.orig/server/vhost.c 2013-10-03 20:31:22.000000000 +0200 +++ httpd-2.4.16/server/vhost.c 2017-02-28 09:06:40.478307333 +0100 @@ -685,6 +685,116 @@ static int vhost_check_config(apr_pool_t * 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 @@ static int vhost_check_config(apr_pool_t * 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 void check_serverpath(request_rec } } +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)
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor