#define SATISFY_ANY 1
#define SATISFY_NOSPEC 2
+/* default maximum of internal redirects */
+# define AP_DEFAULT_MAX_INTERNAL_REDIRECTS 20
+
+/* default maximum subrequest nesting level */
+# define AP_DEFAULT_MAX_SUBREQ_DEPTH 20
+
API_EXPORT(int) ap_allow_options (request_rec *);
API_EXPORT(int) ap_allow_overrides (request_rec *);
API_EXPORT(const char *) ap_default_type (request_rec *);
API_EXPORT(void) ap_custom_response(request_rec *r, int status, char *string);
API_EXPORT(int) ap_exists_config_define(char *name);
+/* Check if the current request is beyond the configured max. number of redirects or subrequests
+ * @param r The current request
+ * @return true (is exceeded) or false
+ */
+API_EXPORT(int) ap_is_recursion_limit_exceeded(const request_rec *r);
+
/* Authentication stuff. This is one of the places where compatibility
* with the old config files *really* hurts; they don't discriminate at
* all between different authentication schemes, meaning that we need
char *access_name;
array_header *sec;
array_header *sec_url;
+
+ /* recursion backstopper */
+ int recursion_limit_set; /* boolean */
+ int redirect_limit; /* maximum number of internal redirects */
+ int subreq_limit; /* maximum nesting level of subrequests */
} core_server_config;
/* for http_config.c */
conf->ap_document_root = is_virtual ? NULL : DOCUMENT_LOCATION;
conf->sec = ap_make_array(a, 40, sizeof(void *));
conf->sec_url = ap_make_array(a, 40, sizeof(void *));
-
+
+ /* recursion stopper */
+ conf->redirect_limit = 0;
+ conf->subreq_limit = 0;
+ conf->recursion_limit_set = 0;
+
return (void *)conf;
}
conf->sec = ap_append_arrays(p, base->sec, virt->sec);
conf->sec_url = ap_append_arrays(p, base->sec_url, virt->sec_url);
+ conf->redirect_limit = virt->recursion_limit_set
+ ? virt->redirect_limit
+ : base->redirect_limit;
+
+ conf->subreq_limit = virt->recursion_limit_set
+ ? virt->subreq_limit
+ : base->subreq_limit;
+
return conf;
}
return NULL;
}
+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 && limit < 4) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 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 && limit < 4) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, cmd->server,
+ "Limiting the subrequest depth to a very low level may"
+ " cause normal requests to fail.");
+ }
+ }
+
+ conf->subreq_limit = limit;
+ conf->recursion_limit_set = 1;
+
+ return NULL;
+}
+
+static void log_backtrace(const request_rec *r)
+{
+ const request_rec *top = r;
+
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r,
+ "r->uri = %s", r->uri ? r->uri : "(unexpectedly NULL)");
+
+ while (top && (top->prev || top->main)) {
+ if (top->prev) {
+ top = top->prev;
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 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|APLOG_NOERRNO, r,
+ "subrequested from r->uri = %s",
+ top->uri ? top->uri : "(unexpectedly NULL)");
+ }
+ }
+}
+
+/*
+ * check whether redirect limit is reached
+ */
+API_EXPORT(int) ap_is_recursion_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, subreqs = 0;
+ int rlimit = conf->recursion_limit_set
+ ? conf->redirect_limit
+ : AP_DEFAULT_MAX_INTERNAL_REDIRECTS;
+ int slimit = conf->recursion_limit_set
+ ? conf->subreq_limit
+ : AP_DEFAULT_MAX_SUBREQ_DEPTH;
+
+ /* fast exit (unlimited) */
+ if (!rlimit && !slimit) {
+ return 0;
+ }
+
+ while (top->prev || top->main) {
+ if (top->prev) {
+ if (rlimit && ++redirects >= rlimit) {
+ /* uuh, too much. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 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.", rlimit);
+
+ /* post backtrace */
+ log_backtrace(r);
+
+ /* return failure */
+ return 1;
+ }
+
+ top = top->prev;
+ }
+
+ if (!top->prev && top->main) {
+ if (slimit && ++subreqs >= slimit) {
+ /* uuh, too much. */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 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.", slimit);
+
+ /* post backtrace */
+ log_backtrace(r);
+
+ /* return failure */
+ return 1;
+ }
+
+ top = top->main;
+ }
+ }
+
+ /* recursion state: ok */
+ return 0;
+}
+
/* Note --- ErrorDocument will now work from .htaccess files.
* The AllowOverride of Fileinfo allows webmasters to turn it off
*/
{ "FileETag", set_etag_bits, NULL, OR_FILEINFO, RAW_ARGS,
"Specify components used to construct a file's ETag"},
+
+{ "LimitInternalRecursion", set_recursion_limit, NULL, RSRC_CONF, TAKE12,
+ "maximum recursion depth of internal redirects and subrequests"},
+
{ NULL }
};
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_recursion_limit_exceeded(r)) {
+ rnew->status = HTTP_INTERNAL_SERVER_ERROR;
+ return rnew;
+ }
+
res = ap_unescape_url(rnew->uri);
if (res) {
rnew->status = res;
ap_set_sub_req_protocol(rnew, r);
fdir = ap_make_dirstr_parent(rnew->pool, r->filename);
+ /* We cannot return NULL without violating the API. So just turn this
+ * subrequest into a 500. */
+ if (ap_is_recursion_limit_exceeded(r)) {
+ rnew->status = HTTP_INTERNAL_SERVER_ERROR;
+ return rnew;
+ }
+
/*
* Check for a special case... if there are no '/' characters in new_file
* at all, then we are looking at a relative lookup in the same
static request_rec *internal_internal_redirect(const char *new_uri, request_rec *r)
{
int access_status;
- request_rec *new = (request_rec *) ap_pcalloc(r->pool, sizeof(request_rec));
+ request_rec *new;
+
+ if (ap_is_recursion_limit_exceeded(r)) {
+ ap_die(HTTP_INTERNAL_SERVER_ERROR, r);
+ return NULL;
+ }
+
+ new = (request_rec *) ap_pcalloc(r->pool, sizeof(request_rec));
new->connection = r->connection;
new->server = r->server;
API_EXPORT(void) ap_internal_redirect(const char *new_uri, request_rec *r)
{
request_rec *new = internal_internal_redirect(new_uri, r);
- process_request_internal(new);
+
+ if (new) {
+ process_request_internal(new);
+ }
}
/* This function is designed for things like actions or CGI scripts, when
API_EXPORT(void) ap_internal_redirect_handler(const char *new_uri, request_rec *r)
{
request_rec *new = internal_internal_redirect(new_uri, r);
- if (r->handler)
- new->content_type = r->content_type;
- process_request_internal(new);
+
+ if (new) {
+ if (r->handler)
+ new->content_type = r->content_type;
+ process_request_internal(new);
+ }
}
/*