Changes with Apache 2.4.11
+ *) SECURITY: CVE-2013-5704 (cve.mitre.org)
+ core: HTTP trailers could be used to replace HTTP headers
+ late during request processing, potentially undoing or
+ otherwise confusing modules that examined or modified
+ request headers earlier. Adds "MergeTrailers" directive to restore
+ legacy behavior. [Edward Lu, Yann Ylavic, Joe Orton, Eric Covener]
+
*) mod_lua: Don't quote Expires and Path values. PR 56734.
[Keith Mashinter, <kmashint yahoo com>]
*) mod_lua: More verbose error logging when a handler function cannot be
found. [Daniel Gruno]
-
Changes with Apache 2.4.9
*) mod_ssl: Work around a bug in some older versions of OpenSSL that
different sections are combined when a request is received</seealso>
</directivesynopsis>
+<directivesynopsis>
+<name>MergeTrailers</name>
+<description>Determins whether trailers are merged into headers</description>
+<syntax>MergeTrailers [on|off]</syntax>
+<default>MergeTrailers off</default>
+<contextlist><context>server config</context><context>virtual host</context></contextlist>
+<compatibility>2.4.10 and later</compatibility>
+
+<usage>
+ <p>This directive controls whether HTTP trailers are copied into the
+ internal representation of HTTP headers. This mergeing occurs when the
+ request body has been completely consumed, long after most header
+ processing would have a chance to examine or modify request headers.</p>
+ <p>This option is provided for compatibility with releases prior to 2.4.10,
+ where trailers were always merged.</p>
+</usage>
+</directivesynopsis>
+
+
</modulesynopsis>
cannot be zero. This is the combination of %I and %O. You need to
enable <module>mod_logio</module> to use this.</td></tr>
+ <tr><td><code>%{<var>VARNAME</var>}^ti</code></td>
+ <td>The contents of <code><var>VARNAME</var>:</code> trailer line(s)
+ in the request sent to the server. </td></tr>
+
+ <tr><td><code>%{<var>VARNAME</var>}^to</code></td>
+ <td>The contents of <code><var>VARNAME</var>:</code> trailer line(s)
+ in the response sent from the server. </td></tr>
+
</table>
<section id="modifiers"><title>Modifiers</title>
* 20120211.35 (2.4.10-dev) Add "r", "must_rebind", and last_backend_conn
to util_ldap_connection_t
* 20120211.36 (2.4.10-dev) Add ap_copy_scoreboard_worker()
+ * 20120211.37 (2.4.11-dev) Add r->trailers_{in,out}
*/
#define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */
#ifndef MODULE_MAGIC_NUMBER_MAJOR
#define MODULE_MAGIC_NUMBER_MAJOR 20120211
#endif
-#define MODULE_MAGIC_NUMBER_MINOR 36 /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 37 /* 0...n */
/**
* Determine if the server's current MODULE_MAGIC_NUMBER is at least a
#define AP_TRACE_ENABLE 1
#define AP_TRACE_EXTENDED 2
int trace_enable;
+#define AP_MERGE_TRAILERS_UNSET 0
+#define AP_MERGE_TRAILERS_ENABLE 1
+#define AP_MERGE_TRAILERS_DISABLE 2
+ int merge_trailers;
} core_server_config;
*/
apr_sockaddr_t *useragent_addr;
char *useragent_ip;
+
+ /** MIME trailer environment from the request */
+ apr_table_t *trailers_in;
+ /** MIME trailer environment from the response */
+ apr_table_t *trailers_out;
};
/**
}
+static apr_status_t read_chunked_trailers(http_ctx_t *ctx, ap_filter_t *f,
+ apr_bucket_brigade *b, int merge)
+{
+ int rv;
+ apr_bucket *e;
+ request_rec *r = f->r;
+ apr_table_t *saved_headers_in = r->headers_in;
+ int saved_status = r->status;
+
+ r->status = HTTP_OK;
+ r->headers_in = r->trailers_in;
+ apr_table_clear(r->headers_in);
+ ctx->state = BODY_NONE;
+ ap_get_mime_headers(r);
+
+ if(r->status == HTTP_OK) {
+ r->status = saved_status;
+ e = apr_bucket_eos_create(f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(b, e);
+ ctx->eos_sent = 1;
+ rv = APR_SUCCESS;
+ }
+ else {
+ const char *error_notes = apr_table_get(r->notes,
+ "error-notes");
+ ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+ "Error while reading HTTP trailer: %i%s%s",
+ r->status, error_notes ? ": " : "",
+ error_notes ? error_notes : "");
+ rv = APR_EINVAL;
+ }
+
+ if(!merge) {
+ r->headers_in = saved_headers_in;
+ }
+ else {
+ r->headers_in = apr_table_overlay(r->pool, saved_headers_in,
+ r->trailers_in);
+ }
+
+ return rv;
+}
+
/* This is the HTTP_INPUT filter for HTTP requests and responses from
* proxied servers (mod_proxy). It handles chunked and content-length
* bodies. This can only be inserted/used after the headers
ap_input_mode_t mode, apr_read_type_e block,
apr_off_t readbytes)
{
+ core_server_config *conf;
apr_bucket *e;
http_ctx_t *ctx = f->ctx;
apr_status_t rv;
int http_error = HTTP_REQUEST_ENTITY_TOO_LARGE;
apr_bucket_brigade *bb;
+ 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);
}
if (!ctx->remaining) {
- /* Handle trailers by calling ap_get_mime_headers again! */
- ctx->state = BODY_NONE;
- ap_get_mime_headers(f->r);
- e = apr_bucket_eos_create(f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(b, e);
- ctx->eos_sent = 1;
- return APR_SUCCESS;
+ return read_chunked_trailers(ctx, f, b,
+ conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE);
}
}
}
}
if (!ctx->remaining) {
- /* Handle trailers by calling ap_get_mime_headers again! */
- ctx->state = BODY_NONE;
- ap_get_mime_headers(f->r);
- e = apr_bucket_eos_create(f->c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(b, e);
- ctx->eos_sent = 1;
- return APR_SUCCESS;
+ return read_chunked_trailers(ctx, f, b,
+ conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE);
}
}
break;
new->main = r->main;
new->headers_in = r->headers_in;
+ new->trailers_in = r->trailers_in;
new->headers_out = apr_table_make(r->pool, 12);
if (ap_is_HTTP_REDIRECT(new->status)) {
const char *location = apr_table_get(r->headers_out, "Location");
apr_table_setn(new->headers_out, "Location", location);
}
new->err_headers_out = r->err_headers_out;
+ new->trailers_out = apr_table_make(r->pool, 5);
new->subprocess_env = rename_original_env(r->pool, r->subprocess_env);
new->notes = apr_table_make(r->pool, 5);
r->headers_out);
r->err_headers_out = apr_table_overlay(r->pool, rr->err_headers_out,
r->err_headers_out);
+ r->trailers_out = apr_table_overlay(r->pool, rr->trailers_out,
+ r->trailers_out);
r->subprocess_env = apr_table_overlay(r->pool, rr->subprocess_env,
r->subprocess_env);
return ap_escape_logitem(r->pool, apr_table_get(r->headers_in, a));
}
+static const char *log_trailer_in(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, apr_table_get(r->trailers_in, a));
+}
+
+
static APR_INLINE char *find_multiple_headers(apr_pool_t *pool,
const apr_table_t *table,
const char *key)
return ap_escape_logitem(r->pool, cp);
}
+static const char *log_trailer_out(request_rec *r, char *a)
+{
+ return ap_escape_logitem(r->pool, apr_table_get(r->trailers_out, a));
+}
+
static const char *log_note(request_rec *r, char *a)
{
return ap_escape_logitem(r->pool, apr_table_get(r->notes, a));
static char *parse_log_item(apr_pool_t *p, log_format_item *it, const char **sa)
{
const char *s = *sa;
- ap_log_handler *handler;
+ ap_log_handler *handler = NULL;
if (*s != '%') {
return parse_log_misc_string(p, it, sa);
break;
default:
- handler = (ap_log_handler *)apr_hash_get(log_hash, s++, 1);
+ /* check for '^' + two character format first */
+ if (*s == '^' && *(s+1) && *(s+2)) {
+ handler = (ap_log_handler *)apr_hash_get(log_hash, s, 3);
+ if (handler) {
+ s += 3;
+ }
+ }
+ if (!handler) {
+ handler = (ap_log_handler *)apr_hash_get(log_hash, s++, 1);
+ }
if (!handler) {
char dummy[2];
log_struct->func = handler;
log_struct->want_orig_default = def;
- apr_hash_set(log_hash, tag, 1, (const void *)log_struct);
+ apr_hash_set(log_hash, tag, strlen(tag), (const void *)log_struct);
}
static ap_log_writer_init *ap_log_set_writer_init(ap_log_writer_init *handle)
{
log_pfn_register(p, "U", log_request_uri, 1);
log_pfn_register(p, "s", log_status, 1);
log_pfn_register(p, "R", log_handler, 1);
+
+ log_pfn_register(p, "^ti", log_trailer_in, 0);
+ log_pfn_register(p, "^to", log_trailer_out, 0);
}
/* reset to default conditions */
rp->status = HTTP_OK;
rp->headers_in = apr_table_make(pool, 50);
+ rp->trailers_in = apr_table_make(pool, 5);
+
rp->subprocess_env = apr_table_make(pool, 50);
rp->headers_out = apr_table_make(pool, 12);
+ rp->trailers_out = apr_table_make(pool, 5);
rp->err_headers_out = apr_table_make(pool, 5);
rp->notes = apr_table_make(pool, 5);
psc = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
r->headers_out = apr_table_make(r->pool, 20);
+ r->trailers_out = apr_table_make(r->pool, 5);
*pread_len = 0;
/*
#define AP_MAX_INTERIM_RESPONSES 10
#endif
+static int add_trailers(void *data, const char *key, const char *val)
+{
+ if (val) {
+ apr_table_add((apr_table_t*)data, key, val);
+ }
+ return 1;
+}
+
static
apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
proxy_conn_rec **backend_ptr,
/* next time try a non-blocking read */
mode = APR_NONBLOCK_READ;
+ if (!apr_is_empty_table(backend->r->trailers_in)) {
+ apr_table_do(add_trailers, r->trailers_out,
+ backend->r->trailers_in, NULL);
+ apr_table_clear(backend->r->trailers_in);
+ }
+
apr_brigade_length(bb, 0, &readbytes);
backend->worker->s->read += readbytes;
#if DEBUGGING
if (virt->error_log_req)
conf->error_log_req = virt->error_log_req;
+ conf->merge_trailers = (virt->merge_trailers != AP_MERGE_TRAILERS_UNSET)
+ ? virt->merge_trailers
+ : base->merge_trailers;
+
return conf;
}
}
+static const char *set_merge_trailers(cmd_parms *cmd, void *dummy, int arg)
+{
+ core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+ &core_module);
+ conf->merge_trailers = (arg ? AP_MERGE_TRAILERS_ENABLE :
+ AP_MERGE_TRAILERS_DISABLE);
+
+ return NULL;
+}
+
/* Note --- ErrorDocument will now work from .htaccess files.
* The AllowOverride of Fileinfo allows webmasters to turn it off
*/
#endif
AP_INIT_TAKE1("TraceEnable", set_trace_enable, NULL, RSRC_CONF,
"'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"),
{ NULL }
};
static int do_nothing(request_rec *r) { return OK; }
-
static int core_override_type(request_rec *r)
{
core_dir_config *conf =
r->status = HTTP_REQUEST_TIME_OUT;
}
else {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
+ "Failed to read request header line %s", field);
r->status = HTTP_BAD_REQUEST;
}
r->allowed_methods = ap_make_method_list(p, 2);
r->headers_in = apr_table_make(r->pool, 25);
+ r->trailers_in = apr_table_make(r->pool, 5);
r->subprocess_env = apr_table_make(r->pool, 25);
r->headers_out = apr_table_make(r->pool, 12);
r->err_headers_out = apr_table_make(r->pool, 5);
+ r->trailers_out = apr_table_make(r->pool, 5);
r->notes = apr_table_make(r->pool, 5);
r->request_config = ap_create_request_config(r->pool);
rnew->status = HTTP_OK;
rnew->headers_in = apr_table_copy(rnew->pool, r->headers_in);
+ rnew->trailers_in = apr_table_copy(rnew->pool, r->trailers_in);
/* did the original request have a body? (e.g. POST w/SSI tags)
* if so, make sure the subrequest doesn't inherit body headers
rnew->subprocess_env = apr_table_copy(rnew->pool, r->subprocess_env);
rnew->headers_out = apr_table_make(rnew->pool, 5);
rnew->err_headers_out = apr_table_make(rnew->pool, 5);
+ rnew->trailers_out = apr_table_make(rnew->pool, 5);
rnew->notes = apr_table_make(rnew->pool, 5);
rnew->expecting_100 = r->expecting_100;