From d048d8b89127019b5a42932321c270ec48b44a6b Mon Sep 17 00:00:00 2001 From: Thierry FOURNIER Date: Thu, 13 Mar 2014 16:46:18 +0100 Subject: [PATCH] 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. --- include/types/log.h | 1 + src/cfgparse.c | 2 +- src/log.c | 8 +++++++- src/proto_http.c | 34 +++++++++++++++++++++++++++++++--- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/include/types/log.h b/include/types/log.h index 66b418dec9..8ee8d7c644 100644 --- a/include/types/log.h +++ b/include/types/log.h @@ -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 diff --git a/src/cfgparse.c b/src/cfgparse.c index 5589ec03f7..5cbb9e2a6b 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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; diff --git a/src/log.c b/src/log.c index 3ab40f9c27..f39168a080 100644 --- a/src/log.c +++ b/src/log.c @@ -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; diff --git a/src/proto_http.c b/src/proto_http.c index c0be289897..0c6a6233f7 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -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);