]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: stats: prevent crash if counters not alloc with dummy one
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Tue, 10 Nov 2020 13:24:31 +0000 (14:24 +0100)
committerChristopher Faulet <cfaulet@haproxy.com>
Thu, 12 Nov 2020 14:16:05 +0000 (15:16 +0100)
Define a per-thread counters allocated with the greatest size of any
stat module counters. This variable is named trash_counters.

When using a proxy without allocated counters, return the trash counters
from EXTRA_COUNTERS_GET instead of a dangling pointer to prevent
segfault.

This is useful for all the proxies used internally and not
belonging to the global proxy list. As these objects does not appears on
the stat report, it does not matter to use the dummy counters.

For this fix to be functional, the extra counters are explicitly
initialized to NULL on proxy/server/listener init functions.

Most notably, the crash has already been detected with the following
vtc:
- reg-tests/lua/txn_get_priv.vtc
- reg-tests/peers/tls_basic_sync.vtc
- reg-tests/peers/tls_basic_sync_wo_stkt_backend.vtc
There is probably other parts that may be impacted (SPOE for example).

This bug was introduced in the current release and do not need to be
backported. The faulty commits are
"MINOR: ssl: count client hello for stats" and
"MINOR: ssl: add counters for ssl sessions".

include/haproxy/stats-t.h
src/listener.c
src/proxy.c
src/server.c
src/stats.c

index af9b9711d7fe186f495d03b8ec6b56780f7a15fe..70d8b489ae57c241667b530c029547d7ba3b0779 100644 (file)
@@ -509,11 +509,15 @@ enum stats_domain_px_cap {
        STATS_PX_CAP_MASK = 0xff
 };
 
+extern THREAD_LOCAL void *trash_counters;
+
 #define EXTRA_COUNTERS(name) \
        struct extra_counters *name
 
 #define EXTRA_COUNTERS_GET(counters, mod) \
-       (void *)((counters)->data + (mod)->counters_off[(counters)->type])
+       (likely(counters) ? \
+               ((void *)((counters)->data + (mod)->counters_off[(counters)->type])) : \
+               (trash_counters))
 
 #define EXTRA_COUNTERS_REGISTER(counters, ctype, alloc_failed_label) \
        do {                                                         \
index 7f038c4a3d7224fc864c24b4fd1a62975ec228f2..c9f0c2de6ac70645dbd8ec48690ac5cdc9fe56b3 100644 (file)
@@ -623,6 +623,8 @@ int create_listeners(struct bind_conf *bc, const struct sockaddr_storage *ss,
                if (fd != -1)
                        l->rx.flags |= RX_F_INHERITED;
 
+               l->extra_counters = NULL;
+
                HA_SPIN_INIT(&l->lock);
                _HA_ATOMIC_ADD(&jobs, 1);
                _HA_ATOMIC_ADD(&listeners, 1);
index e469c76714d5b05f6e2c6c0a56690d22fc78c3c7..08140198f49e58e489ee748bbbe0a21287a58709 100644 (file)
@@ -1041,6 +1041,9 @@ void init_new_proxy(struct proxy *p)
        /* Default to only allow L4 retries */
        p->retry_type = PR_RE_CONN_FAILED;
 
+       p->extra_counters_fe = NULL;
+       p->extra_counters_be = NULL;
+
        HA_RWLOCK_INIT(&p->lock);
 }
 
index 1e9f46ec7264e4f8281bacd89fa18a2d8a6278a4..d72e7e069c6cbfda54be39f8b88587e0ed3ca059 100644 (file)
@@ -1742,6 +1742,8 @@ struct server *new_server(struct proxy *proxy)
        srv->agent.proxy = proxy;
        srv->xprt  = srv->check.xprt = srv->agent.xprt = xprt_get(XPRT_RAW);
 
+       srv->extra_counters = NULL;
+
        /* please don't put default server settings here, they are set in
         * init_default_instance().
         */
index ad92d71593bb809d81355a58c19ab48a03616e29..3f314440de6d0f064aa1278cfbb7a7840579b9f1 100644 (file)
@@ -269,6 +269,8 @@ static struct list stats_module_list[STATS_DOMAIN_COUNT] = {
        LIST_HEAD_INIT(stats_module_list[STATS_DOMAIN_DNS]),
 };
 
+THREAD_LOCAL void *trash_counters;
+
 static inline uint8_t stats_get_domain(uint32_t domain)
 {
        return domain >> STATS_DOMAIN & STATS_DOMAIN_MASK;
@@ -4548,6 +4550,34 @@ static int allocate_stats_dns_postcheck(void)
 
 REGISTER_CONFIG_POSTPARSER("allocate-stats-dns", allocate_stats_dns_postcheck);
 
+static int allocate_trash_counters(void)
+{
+       struct stats_module *mod;
+       int domains[] = { STATS_DOMAIN_PROXY, STATS_DOMAIN_DNS }, i;
+       size_t max_counters_size = 0;
+
+       /* calculate the greatest counters used by any stats modules */
+       for (i = 0; i < STATS_DOMAIN_COUNT; ++i) {
+               list_for_each_entry(mod, &stats_module_list[domains[i]], list) {
+                       max_counters_size = mod->counters_size > max_counters_size ?
+                                           mod->counters_size : max_counters_size;
+               }
+       }
+
+       /* allocate the trash with the size of the greatest counters */
+       if (max_counters_size) {
+               trash_counters = malloc(max_counters_size);
+               if (!trash_counters) {
+                       ha_alert("stats: cannot allocate trash counters for statistics\n");
+                       return 0;
+               }
+       }
+
+       return 1;
+}
+
+REGISTER_PER_THREAD_ALLOC(allocate_trash_counters);
+
 static void deinit_stats(void)
 {
        int domains[] = { STATS_DOMAIN_PROXY, STATS_DOMAIN_DNS }, i;
@@ -4565,6 +4595,14 @@ static void deinit_stats(void)
 
 REGISTER_POST_DEINIT(deinit_stats);
 
+static void free_trash_counters(void)
+{
+       if (trash_counters)
+               free(trash_counters);
+}
+
+REGISTER_PER_THREAD_FREE(free_trash_counters);
+
 /* register cli keywords */
 static struct cli_kw_list cli_kws = {{ },{
        { { "clear", "counters",  NULL }, "clear counters : clear max statistics counters (add 'all' for all counters)", cli_parse_clear_counters, NULL, NULL },