diff --git a/CHANGES b/CHANGES index 2debfde428..966119f772 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,12 @@ Changes with Apache 2.1.0-dev [Remove entries to the current 2.0 section below, when backported] + *) Prevent the server from crashing when entering infinite loops. The + new LimitInternalRecursion directive configures limits of subsequent + internal redirects and nested subrequests, after which the request + will be aborted. PR 19753 (and probably others). + [William Rowe, Jeff Trawick, André Malo] + *) mod_rewrite: Fix LA-U variable look ahead which didn't work correctly in directory context. Related to PR 8493. [André Malo] diff --git a/include/http_core.h b/include/http_core.h index 1e700f8cad..b4d8068bbf 100644 --- a/include/http_core.h +++ b/include/http_core.h @@ -134,6 +134,12 @@ extern "C" { */ #define AP_MIN_BYTES_TO_WRITE 8000 +/* default maximum of internal redirects */ +# define AP_DEFAULT_MAX_INTERNAL_REDIRECTS 10 + +/* default maximum subrequest nesting level */ +# define AP_DEFAULT_MAX_SUBREQ_DEPTH 10 + /** * Retrieve the value of Options for this request * @param r The current request @@ -257,6 +263,22 @@ AP_DECLARE(size_t) ap_get_limit_xml_body(const request_rec *r); */ AP_DECLARE(void) ap_custom_response(request_rec *r, int status, const char *string); +/** + * Check if the current request is beyond the configured max. number of redirects + * @param r The current request + * @return true (is exceeded) or false + * @deffunc int ap_is_redirect_limit_exceeded(const request_rec *r) + */ +AP_DECLARE(int) ap_is_redirect_limit_exceeded(const request_rec *r); + +/** + * Check if the current request is beyond the configured subreq nesting level + * @param r The current request + * @return true (is exceeded) or false + * @deffunc int ap_is_subreq_limit_exceeded(const request_rec *r) + */ +AP_DECLARE(int) ap_is_subreq_limit_exceeded(const request_rec *r); + /** * Check for a definition from the server command line * @param name The define to check for @@ -560,6 +582,10 @@ typedef struct { char *access_name; apr_array_header_t *sec_dir; apr_array_header_t *sec_url; + + /* recursion backstopper */ + int redirect_limit; /* maximum number of internal redirects */ + int subreq_limit; /* maximum nesting level of subrequests */ } core_server_config; /* for AddOutputFiltersByType in core.c */ diff --git a/modules/http/http_request.c b/modules/http/http_request.c index 0817bba7b6..ea2fca452c 100644 --- a/modules/http/http_request.c +++ b/modules/http/http_request.c @@ -332,8 +332,14 @@ static apr_table_t *rename_original_env(apr_pool_t *p, apr_table_t *t) static request_rec *internal_internal_redirect(const char *new_uri, request_rec *r) { int access_status; - request_rec *new = (request_rec *) apr_pcalloc(r->pool, - sizeof(request_rec)); + request_rec *new; + + if (ap_is_redirect_limit_exceeded(r)) { + ap_die(HTTP_INTERNAL_SERVER_ERROR, r); + return NULL; + } + + new = (request_rec *) apr_pcalloc(r->pool, sizeof(request_rec)); new->connection = r->connection; new->server = r->server; @@ -499,7 +505,14 @@ AP_DECLARE(void) ap_internal_fast_redirect(request_rec *rr, request_rec *r) AP_DECLARE(void) ap_internal_redirect(const char *new_uri, request_rec *r) { request_rec *new = internal_internal_redirect(new_uri, r); - int access_status = ap_process_request_internal(new); + int access_status; + + /* ap_die was already called, if an error occured */ + if (!new) { + return; + } + + access_status = ap_process_request_internal(new); if (access_status == OK) { if ((access_status = ap_invoke_handler(new)) != 0) { ap_die(access_status, new); @@ -520,6 +533,12 @@ AP_DECLARE(void) ap_internal_redirect_handler(const char *new_uri, request_rec * { int access_status; request_rec *new = internal_internal_redirect(new_uri, r); + + /* ap_die was already called, if an error occured */ + if (!new) { + return; + } + if (r->handler) ap_set_content_type(new, r->content_type); access_status = ap_process_request_internal(new); diff --git a/server/core.c b/server/core.c index 8897d9cf9e..0c26d129d7 100644 --- a/server/core.c +++ b/server/core.c @@ -468,6 +468,10 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s) conf->sec_dir = apr_array_make(a, 40, sizeof(ap_conf_vector_t *)); conf->sec_url = apr_array_make(a, 40, sizeof(ap_conf_vector_t *)); + /* recursion stopper */ + conf->redirect_limit = 0; /* 0 == unset */ + conf->subreq_limit = 0; + return (void *)conf; } @@ -491,6 +495,14 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) conf->sec_dir = apr_array_append(p, base->sec_dir, virt->sec_dir); conf->sec_url = apr_array_append(p, base->sec_url, virt->sec_url); + conf->redirect_limit = virt->redirect_limit + ? virt->redirect_limit + : base->redirect_limit; + + conf->subreq_limit = virt->subreq_limit + ? virt->subreq_limit + : base->subreq_limit; + return conf; } @@ -2613,6 +2625,141 @@ static const char *set_limit_nproc(cmd_parms *cmd, void *conf_, } #endif +static const char *set_recursion_limit(cmd_parms *cmd, void *dummy, + const char *arg1, const char *arg2) +{ + core_server_config *conf = ap_get_module_config(cmd->server->module_config, + &core_module); + int limit = atoi(arg1); + + if (limit <= 0) { + return "The recursion limit must be greater than zero."; + } + if (limit < 4) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + "Limiting internal redirects to very low numbers may " + "cause normal requests to fail."); + } + + conf->redirect_limit = limit; + + if (arg2) { + limit = atoi(arg2); + + if (limit <= 0) { + return "The recursion limit must be greater than zero."; + } + if (limit < 4) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, + "Limiting the subrequest depth to a very low level may" + " cause normal requests to fail."); + } + } + + conf->subreq_limit = limit; + + return NULL; +} + +static void log_backtrace(const request_rec *top, const request_rec *r) +{ + while (top) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "redirected from r->uri = %s", + top->uri ? top->uri : "(unexpectedly NULL)"); + + if (!top->prev && top->main) { + top = top->main; + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "subrequested from r->uri = %s", + top->uri ? top->uri : "(unexpectedly NULL)"); + } + else { + top = top->prev; + } + } +} + +/* + * check whether redirect limit is reached + */ +AP_DECLARE(int) ap_is_redirect_limit_exceeded(const request_rec *r) +{ + core_server_config *conf = ap_get_module_config(r->server->module_config, + &core_module); + const request_rec *top = r; + int redirects = 0; + int limit = conf->redirect_limit + ? conf->redirect_limit + : AP_DEFAULT_MAX_INTERNAL_REDIRECTS; + + while (top->prev) { + if (++redirects >= limit) { + /* uuh, too much. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Request exceeded the limit of %d internal redirects " + "due to probable configuration error. Use " + "'LimitInternalRecursion' to increase the limit if " + "necessary. Use 'LogLevel debug' to get a " + "backtrace.", limit); + + /* post backtrace */ + log_backtrace(r->prev, r); + + /* return failure */ + return 1; + } + + if (!top->prev && top->main) { + top = top->main; + } + else { + top = top->prev; + } + } + + /* number of redirects is ok */ + return 0; +} + +/* + * check whether subrequest depth limit is reached + */ +AP_DECLARE(int) ap_is_subreq_limit_exceeded(const request_rec *r) +{ + core_server_config *conf = ap_get_module_config(r->server->module_config, + &core_module); + const request_rec *top = r; + int subreqs = 0; + int limit = conf->subreq_limit + ? conf->subreq_limit + : AP_DEFAULT_MAX_SUBREQ_DEPTH; + + while (top->main) { + if (++subreqs >= limit) { + /* uuh, too much. */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "Request exceeded the limit of %d subrequest " + "nesting levels due to probable confguration error. " + "Use 'LimitInternalRecursion' to increase the limit " + "if necessary. Use 'LogLevel debug' to get a " + "backtrace.", limit); + + /* post backtrace */ + log_backtrace(r->main, r); + + /* return failure */ + return 1; + } + + top = top->main; + } + + /* number of subrequests is ok */ + return 0; +} + static const char *add_ct_output_filters(cmd_parms *cmd, void *conf_, const char *arg, const char *arg2) { @@ -3063,6 +3210,10 @@ AP_INIT_TAKE12("RLimitNPROC", no_set_limit, NULL, OR_ALL, "soft/hard limits for max number of processes per uid"), #endif +/* internal recursion stopper */ +AP_INIT_TAKE12("LimitInternalRecursion", set_recursion_limit, NULL, RSRC_CONF, + "maximum recursion depth of internal redirects and subrequests"), + AP_INIT_TAKE1("ForceType", ap_set_string_slot_lower, (void *)APR_OFFSETOF(core_dir_config, mime_type), OR_FILEINFO, "a mime type that overrides other configured type"), diff --git a/server/request.c b/server/request.c index 09e784a12b..4254c8e446 100644 --- a/server/request.c +++ b/server/request.c @@ -1631,6 +1631,14 @@ AP_DECLARE(request_rec *) ap_sub_req_method_uri(const char *method, udir = ap_escape_uri(rnew->pool, udir); /* re-escape it */ ap_parse_uri(rnew, ap_make_full_path(rnew->pool, udir, new_file)); } + + /* We cannot return NULL without violating the API. So just turn this + * subrequest into a 500 to indicate the failure. */ + if (ap_is_subreq_limit_exceeded(r)) { + rnew->status = HTTP_INTERNAL_SERVER_ERROR; + return rnew; + } + /* lookup_uri * If the content can be served by the quick_handler, we can * safely bypass request_internal processing. @@ -1764,6 +1772,13 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_dirent(const apr_finfo_t *dirent, ap_parse_uri(rnew, rnew->uri); } + /* We cannot return NULL without violating the API. So just turn this + * subrequest into a 500. */ + if (ap_is_subreq_limit_exceeded(r)) { + rnew->status = HTTP_INTERNAL_SERVER_ERROR; + return rnew; + } + if ((res = ap_process_request_internal(rnew))) { rnew->status = res; } @@ -1851,6 +1866,13 @@ AP_DECLARE(request_rec *) ap_sub_req_lookup_file(const char *new_file, rnew->uri = apr_pstrdup(rnew->pool, ""); } + /* We cannot return NULL without violating the API. So just turn this + * subrequest into a 500. */ + if (ap_is_subreq_limit_exceeded(r)) { + rnew->status = HTTP_INTERNAL_SERVER_ERROR; + return rnew; + } + if ((res = ap_process_request_internal(rnew))) { rnew->status = res; }