Hi all, Here's a patch that fixes the problem with trash buffers being overwritten during build_logline() execution.
Thanks. Best regards, Dragan Dosen
>From a5652cdbdbc71e4d303f28e6cacd7bdad263409f Mon Sep 17 00:00:00 2001 From: Dragan Dosen <ddo...@haproxy.com> Date: Thu, 26 Oct 2017 11:25:10 +0200 Subject: [PATCH] BUG/MEDIUM: prevent buffers being overwritten during build_logline() execution Calls to build_logline() are audited in order to use dynamic trash buffers allocated by alloc_trash_chunk() instead of global trash buffers. This is similar to commits 07a0fec ("BUG/MEDIUM: http: Prevent replace-header from overwriting a buffer") and 0d94576 ("BUG/MEDIUM: http: prevent redirect from overwriting a buffer"). This patch should be backported in 1.7, 1.6 and 1.5. It relies on commit b686afd ("MINOR: chunks: implement a simple dynamic allocator for trash buffers") for the trash allocator, which has to be backported as well. --- src/proto_http.c | 218 ++++++++++++++++++++++++++++++++++--------------------- src/stream.c | 18 +++-- 2 files changed, 149 insertions(+), 87 deletions(-) diff --git a/src/proto_http.c b/src/proto_http.c index 0662041..c81409f 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -2555,19 +2555,25 @@ resume_execution: break; case ACT_HTTP_SET_HDR: - case ACT_HTTP_ADD_HDR: + case ACT_HTTP_ADD_HDR: { /* The scope of the trash buffer must be limited to this function. The * build_logline() function can execute a lot of other function which * can use the trash buffer. So for limiting the scope of this global * buffer, we build first the header value using build_logline, and * after we store the header name. */ + struct chunk *replace; + + replace = alloc_trash_chunk(); + if (!replace) + return HTTP_RULE_RES_BADREQ; + len = rule->arg.hdr_add.name_len + 2, - len += build_logline(s, trash.str + len, trash.size - len, &rule->arg.hdr_add.fmt); - memcpy(trash.str, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len); - trash.str[rule->arg.hdr_add.name_len] = ':'; - trash.str[rule->arg.hdr_add.name_len + 1] = ' '; - trash.len = len; + len += build_logline(s, replace->str + len, replace->size - len, &rule->arg.hdr_add.fmt); + memcpy(replace->str, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len); + replace->str[rule->arg.hdr_add.name_len] = ':'; + replace->str[rule->arg.hdr_add.name_len + 1] = ' '; + replace->len = len; if (rule->action == ACT_HTTP_SET_HDR) { /* remove all occurrences of the header */ @@ -2578,90 +2584,105 @@ resume_execution: } } - http_header_add_tail2(&txn->req, &txn->hdr_idx, trash.str, trash.len); + http_header_add_tail2(&txn->req, &txn->hdr_idx, replace->str, replace->len); + + free_trash_chunk(replace); break; + } case ACT_HTTP_DEL_ACL: case ACT_HTTP_DEL_MAP: { struct pat_ref *ref; - char *key; - int len; + struct chunk *key; /* collect reference */ ref = pat_ref_lookup(rule->arg.map.ref); if (!ref) continue; + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) + return HTTP_RULE_RES_BADREQ; + /* collect key */ - len = build_logline(s, trash.str, trash.size, &rule->arg.map.key); - key = trash.str; - key[len] = '\0'; + key->len = build_logline(s, key->str, key->size, &rule->arg.map.key); + key->str[key->len] = '\0'; /* perform update */ /* returned code: 1=ok, 0=ko */ - pat_ref_delete(ref, key); + pat_ref_delete(ref, key->str); + free_trash_chunk(key); break; } case ACT_HTTP_ADD_ACL: { struct pat_ref *ref; - char *key; - struct chunk *trash_key; - int len; - - trash_key = get_trash_chunk(); + struct chunk *key; /* collect reference */ ref = pat_ref_lookup(rule->arg.map.ref); if (!ref) continue; + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) + return HTTP_RULE_RES_BADREQ; + /* collect key */ - len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key); - key = trash_key->str; - key[len] = '\0'; + key->len = build_logline(s, key->str, key->size, &rule->arg.map.key); + key->str[key->len] = '\0'; /* perform update */ /* add entry only if it does not already exist */ - if (pat_ref_find_elt(ref, key) == NULL) - pat_ref_add(ref, key, NULL, NULL); + if (pat_ref_find_elt(ref, key->str) == NULL) + pat_ref_add(ref, key->str, NULL, NULL); + free_trash_chunk(key); break; } case ACT_HTTP_SET_MAP: { struct pat_ref *ref; - char *key, *value; - struct chunk *trash_key, *trash_value; - int len; - - trash_key = get_trash_chunk(); - trash_value = get_trash_chunk(); + struct chunk *key, *value; /* collect reference */ ref = pat_ref_lookup(rule->arg.map.ref); if (!ref) continue; + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) + return HTTP_RULE_RES_BADREQ; + + /* allocate value */ + value = alloc_trash_chunk(); + if (!value) { + free_trash_chunk(key); + return HTTP_RULE_RES_BADREQ; + } + /* collect key */ - len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key); - key = trash_key->str; - key[len] = '\0'; + key->len = build_logline(s, key->str, key->size, &rule->arg.map.key); + key->str[key->len] = '\0'; /* collect value */ - len = build_logline(s, trash_value->str, trash_value->size, &rule->arg.map.value); - value = trash_value->str; - value[len] = '\0'; + value->len = build_logline(s, value->str, value->size, &rule->arg.map.value); + value->str[value->len] = '\0'; /* perform update */ - if (pat_ref_find_elt(ref, key) != NULL) + if (pat_ref_find_elt(ref, key->str) != NULL) /* update entry if it exists */ - pat_ref_set(ref, key, value, NULL); + pat_ref_set(ref, key->str, value->str, NULL); else /* insert a new entry */ - pat_ref_add(ref, key, value, NULL); + pat_ref_add(ref, key->str, value->str, NULL); + free_trash_chunk(key); + free_trash_chunk(value); break; } @@ -2824,13 +2845,20 @@ resume_execution: break; case ACT_HTTP_SET_HDR: - case ACT_HTTP_ADD_HDR: - chunk_printf(&trash, "%s: ", rule->arg.hdr_add.name); - memcpy(trash.str, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len); - trash.len = rule->arg.hdr_add.name_len; - trash.str[trash.len++] = ':'; - trash.str[trash.len++] = ' '; - trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.hdr_add.fmt); + case ACT_HTTP_ADD_HDR: { + struct chunk *replace; + + replace = alloc_trash_chunk(); + if (!replace) + return HTTP_RULE_RES_BADREQ; + + chunk_printf(replace, "%s: ", rule->arg.hdr_add.name); + memcpy(replace->str, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len); + replace->len = rule->arg.hdr_add.name_len; + replace->str[replace->len++] = ':'; + replace->str[replace->len++] = ' '; + replace->len += build_logline(s, replace->str + replace->len, replace->size - replace->len, + &rule->arg.hdr_add.fmt); if (rule->action == ACT_HTTP_SET_HDR) { /* remove all occurrences of the header */ @@ -2840,90 +2868,105 @@ resume_execution: http_remove_header2(&txn->rsp, &txn->hdr_idx, &ctx); } } - http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.str, trash.len); + http_header_add_tail2(&txn->rsp, &txn->hdr_idx, replace->str, replace->len); + + free_trash_chunk(replace); break; + } case ACT_HTTP_DEL_ACL: case ACT_HTTP_DEL_MAP: { struct pat_ref *ref; - char *key; - int len; + struct chunk *key; /* collect reference */ ref = pat_ref_lookup(rule->arg.map.ref); if (!ref) continue; + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) + return HTTP_RULE_RES_BADREQ; + /* collect key */ - len = build_logline(s, trash.str, trash.size, &rule->arg.map.key); - key = trash.str; - key[len] = '\0'; + key->len = build_logline(s, key->str, key->size, &rule->arg.map.key); + key->str[key->len] = '\0'; /* perform update */ /* returned code: 1=ok, 0=ko */ - pat_ref_delete(ref, key); + pat_ref_delete(ref, key->str); + free_trash_chunk(key); break; } case ACT_HTTP_ADD_ACL: { struct pat_ref *ref; - char *key; - struct chunk *trash_key; - int len; - - trash_key = get_trash_chunk(); + struct chunk *key; /* collect reference */ ref = pat_ref_lookup(rule->arg.map.ref); if (!ref) continue; + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) + return HTTP_RULE_RES_BADREQ; + /* collect key */ - len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key); - key = trash_key->str; - key[len] = '\0'; + key->len = build_logline(s, key->str, key->size, &rule->arg.map.key); + key->str[key->len] = '\0'; /* perform update */ /* check if the entry already exists */ - if (pat_ref_find_elt(ref, key) == NULL) - pat_ref_add(ref, key, NULL, NULL); + if (pat_ref_find_elt(ref, key->str) == NULL) + pat_ref_add(ref, key->str, NULL, NULL); + free_trash_chunk(key); break; } case ACT_HTTP_SET_MAP: { struct pat_ref *ref; - char *key, *value; - struct chunk *trash_key, *trash_value; - int len; - - trash_key = get_trash_chunk(); - trash_value = get_trash_chunk(); + struct chunk *key, *value; /* collect reference */ ref = pat_ref_lookup(rule->arg.map.ref); if (!ref) continue; + /* allocate key */ + key = alloc_trash_chunk(); + if (!key) + return HTTP_RULE_RES_BADREQ; + + /* allocate value */ + value = alloc_trash_chunk(); + if (!value) { + free_trash_chunk(key); + return HTTP_RULE_RES_BADREQ; + } + /* collect key */ - len = build_logline(s, trash_key->str, trash_key->size, &rule->arg.map.key); - key = trash_key->str; - key[len] = '\0'; + key->len = build_logline(s, key->str, key->size, &rule->arg.map.key); + key->str[key->len] = '\0'; /* collect value */ - len = build_logline(s, trash_value->str, trash_value->size, &rule->arg.map.value); - value = trash_value->str; - value[len] = '\0'; + value->len = build_logline(s, value->str, value->size, &rule->arg.map.value); + value->str[value->len] = '\0'; /* perform update */ - if (pat_ref_find_elt(ref, key) != NULL) + if (pat_ref_find_elt(ref, key->str) != NULL) /* update entry if it exists */ - pat_ref_set(ref, key, value, NULL); + pat_ref_set(ref, key->str, value->str, NULL); else /* insert a new entry */ - pat_ref_add(ref, key, value, NULL); + pat_ref_add(ref, key->str, value->str, NULL); + free_trash_chunk(key); + free_trash_chunk(value); break; } @@ -11678,15 +11721,26 @@ void http_set_status(unsigned int status, const char *reason, struct stream *s) enum act_return http_action_set_req_line(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int flags) { - chunk_reset(&trash); + struct chunk *replace; + enum act_return ret = ACT_RET_ERR; + + replace = alloc_trash_chunk(); + if (!replace) + goto leave; /* If we have to create a query string, prepare a '?'. */ if (rule->arg.http.action == 2) - trash.str[trash.len++] = '?'; - trash.len += build_logline(s, trash.str + trash.len, trash.size - trash.len, &rule->arg.http.logfmt); + replace->str[replace->len++] = '?'; + replace->len += build_logline(s, replace->str + replace->len, replace->size - replace->len, + &rule->arg.http.logfmt); - http_replace_req_line(rule->arg.http.action, trash.str, trash.len, px, s); - return ACT_RET_CONT; + http_replace_req_line(rule->arg.http.action, replace->str, replace->len, px, s); + + ret = ACT_RET_CONT; + +leave: + free_trash_chunk(replace); + return ret; } /* This function is just a compliant action wrapper for "set-status". */ diff --git a/src/stream.c b/src/stream.c index 2e9b95c..a78ee96 100644 --- a/src/stream.c +++ b/src/stream.c @@ -1186,13 +1186,21 @@ static int process_switching_rules(struct stream *s, struct channel *req, int an * If we can't resolve the name, or if any error occurs, break * the loop and fallback to the default backend. */ - struct proxy *backend; + struct proxy *backend = NULL; if (rule->dynamic) { - struct chunk *tmp = get_trash_chunk(); - if (!build_logline(s, tmp->str, tmp->size, &rule->be.expr)) - break; - backend = proxy_be_by_name(tmp->str); + struct chunk *tmp; + + tmp = alloc_trash_chunk(); + if (!tmp) + goto sw_failed; + + if (build_logline(s, tmp->str, tmp->size, &rule->be.expr)) + backend = proxy_be_by_name(tmp->str); + + free_trash_chunk(tmp); + tmp = NULL; + if (!backend) break; } -- 2.7.4