BUG/MINOR: http: fix encoding of samples used in http headers

The binary samples are sometimes copied as is into http headers.
A sample can contain bytes unallowed by the http rfc concerning
header content, for example if it was extracted from binary data.
The resulting http request can thus be invalid.

This issue does not yet happen because haproxy currently (mistakenly)
hex-encodes binary data, so it is not really possible to retrieve
invalid HTTP chars.

The solution consists in hex-encoding all non-printable chars prefixed
by a '%' sign.

No backport is needed since existing code is not affected yet.
This commit is contained in:
Thierry FOURNIER 2014-03-13 16:46:18 +01:00 committed by Willy Tarreau
parent e059ec9393
commit d048d8b891
4 changed files with 40 additions and 5 deletions

View File

@ -125,6 +125,7 @@ struct logformat_node {
#define LOG_OPT_QUOTE 0x00000004
#define LOG_OPT_REQ_CAP 0x00000008
#define LOG_OPT_RES_CAP 0x00000010
#define LOG_OPT_HTTP 0x00000020
/* Fields that need to be extracted from the incoming connection or request for

View File

@ -7085,7 +7085,7 @@ out_uri_auth_compat:
curproxy->conf.args.ctx = ARGC_UIF;
curproxy->conf.args.file = curproxy->conf.uif_file;
curproxy->conf.args.line = curproxy->conf.uif_line;
parse_logformat_string(curproxy->conf.uniqueid_format_string, curproxy, &curproxy->format_unique_id, 0,
parse_logformat_string(curproxy->conf.uniqueid_format_string, curproxy, &curproxy->format_unique_id, LOG_OPT_HTTP,
(proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR);
curproxy->conf.args.file = NULL;
curproxy->conf.args.line = 0;

View File

@ -885,6 +885,7 @@ void __send_log(struct proxy *p, int level, char *message, size_t size)
extern fd_set hdr_encode_map[];
extern fd_set url_encode_map[];
extern fd_set http_encode_map[];
const char sess_cookie[8] = "NIDVEOU7"; /* No cookie, Invalid cookie, cookie for a Down server, Valid cookie, Expired cookie, Old cookie, Unused, unknown */
@ -940,6 +941,7 @@ int build_logline(struct session *s, char *dst, size_t maxsize, struct list *lis
struct connection *conn;
const char *src = NULL;
struct sample *key;
const struct chunk empty = { NULL, 0, 0 };
switch (tmp->type) {
case LOG_FMT_SEPARATOR:
@ -964,7 +966,11 @@ int build_logline(struct session *s, char *dst, size_t maxsize, struct list *lis
key = sample_fetch_string(be, s, txn, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, tmp->expr);
if (!key && (tmp->options & LOG_OPT_RES_CAP))
key = sample_fetch_string(be, s, txn, SMP_OPT_DIR_RES|SMP_OPT_FINAL, tmp->expr);
ret = lf_text_len(tmplog, key ? key->data.str.str : NULL, key ? key->data.str.len : 0, dst + maxsize - tmplog, tmp);
if (tmp->options & LOG_OPT_HTTP)
ret = encode_chunk(tmplog, dst + maxsize,
'%', http_encode_map, key ? &key->data.str : &empty);
else
ret = lf_text_len(tmplog, key ? key->data.str.str : NULL, key ? key->data.str.len : 0, dst + maxsize - tmplog, tmp);
if (ret == 0)
goto out;
tmplog = ret;

View File

@ -235,6 +235,7 @@ static struct hdr_ctx static_hdr_ctx;
*/
fd_set hdr_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];
fd_set url_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];
fd_set http_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set))];
#else
#error "Check if your OS uses bitfields for fd_sets"
@ -263,6 +264,7 @@ void init_proto_http()
*/
memset(hdr_encode_map, 0, sizeof(hdr_encode_map));
memset(url_encode_map, 0, sizeof(url_encode_map));
memset(http_encode_map, 0, sizeof(url_encode_map));
for (i = 0; i < 32; i++) {
FD_SET(i, hdr_encode_map);
FD_SET(i, url_encode_map);
@ -284,6 +286,32 @@ void init_proto_http()
tmp++;
}
/* initialize the http header encoding map. The draft httpbis define the
* header content as:
*
* HTTP-message = start-line
* *( header-field CRLF )
* CRLF
* [ message-body ]
* header-field = field-name ":" OWS field-value OWS
* field-value = *( field-content / obs-fold )
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* obs-fold = CRLF 1*( SP / HTAB )
* field-vchar = VCHAR / obs-text
* VCHAR = %x21-7E
* obs-text = %x80-FF
*
* All the chars are encoded except "VCHAR", "obs-text", SP and HTAB.
* The encoded chars are form 0x00 to 0x08, 0x0a to 0x1f and 0x7f. The
* "obs-fold" is volontary forgotten because haproxy remove this.
*/
memset(http_encode_map, 0, sizeof(http_encode_map));
for (i = 0x00; i <= 0x08; i++)
FD_SET(i, http_encode_map);
for (i = 0x0a; i <= 0x1f; i++)
FD_SET(i, http_encode_map);
FD_SET(0x7f, http_encode_map);
/* memory allocations */
pool2_requri = create_pool("requri", REQURI_LEN, MEM_F_SHARED);
pool2_uniqueid = create_pool("uniqueid", UNIQUEID_LEN, MEM_F_SHARED);
@ -8459,7 +8487,7 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
LIST_INIT(&rule->arg.hdr_add.fmt);
proxy->conf.args.ctx = ARGC_HRQ;
parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, 0,
parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, LOG_OPT_HTTP,
(proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR);
free(proxy->conf.lfs_file);
proxy->conf.lfs_file = strdup(proxy->conf.args.file);
@ -8630,7 +8658,7 @@ struct http_res_rule *parse_http_res_cond(const char **args, const char *file, i
LIST_INIT(&rule->arg.hdr_add.fmt);
proxy->conf.args.ctx = ARGC_HRS;
parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, 0,
parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, LOG_OPT_HTTP,
(proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR);
free(proxy->conf.lfs_file);
proxy->conf.lfs_file = strdup(proxy->conf.args.file);
@ -8786,7 +8814,7 @@ struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, st
*/
proxy->conf.args.ctx = ARGC_RDR;
if (!(type == REDIRECT_TYPE_PREFIX && destination[0] == '/' && destination[1] == '\0')) {
parse_logformat_string(destination, curproxy, &rule->rdr_fmt, 0,
parse_logformat_string(destination, curproxy, &rule->rdr_fmt, LOG_OPT_HTTP,
(curproxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR);
free(curproxy->conf.lfs_file);
curproxy->conf.lfs_file = strdup(curproxy->conf.args.file);