Hi,
Here are the patches for GitHub issues #3545 and #3546, as Amaury
suggested in #3547, against current master (8c84ab562). They are attached
(git format-patch) and independent of each other.
1/2 BUG/MINOR: sample: fix NULL dereference in the ltime/utime converters
conv_time_common() returns NULL when the expanded format does not fit
in the trash chunk or when alloc_trash_chunk() fails, and its four
callers dereference it. With ms_ltime("%N" x 3000) the config is
accepted and the first request crashes the process. The converters
now fail instead. Backport to 2.9 and later.
2/2 BUG/MINOR: debug: fix "debug dev fd" crash on unknown address families
sa2str() returns NULL for families other than AF_INET, AF_INET6 and
AF_UNIX, and "debug dev fd" passes it to strlen(). With an inherited
AF_NETLINK socket the command crashes the process. It now prints "?".
Backport to 3.1 and later.
Tested on 8c84ab562, release build (USE_OPENSSL=1): on master all four
converters (ms_ltime, us_ltime, ms_utime, us_utime) and "debug dev fd" with
an inherited netlink socket crash with SIGSEGV; with the patches each
converter request returns 200, "debug dev fd" prints "laddr=? raddr=?" for
the netlink fd, and the process keeps running.
Both issues were found with a static analysis engine (Infer/Pulse) and
verified manually.
Martin Mirchev
From f58f3fead62290c7c94222e1ed4228b0746bc394 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 28 Oct 2026 22:33:16 +0800
Subject: [PATCH 1/2] BUG/MINOR: sample: fix NULL dereference in the
ltime/utime converters
conv_time_common() returns NULL when alloc_trash_chunk() fails, and when
the format string expanded for "%N" does not fit in the trash chunk. Its
four callers, sample_conv_us_ltime(), sample_conv_ms_ltime(),
sample_conv_us_utime() and sample_conv_ms_utime(), dereference the
result without checking it.
A configuration using one of these converters with a long format, e.g.
ms_ltime("%N%N%N...") expanding to more than bufsize, is accepted by
"haproxy -c", and the first request evaluating it crashes the process
with a NULL dereference. A failed allocation at runtime has the same
effect with an ordinary format.
Let's make the converter fail instead, like on its other error paths.
This fixes GitHub issue #3545.
This must be backported to all stable versions starting with 2.9, where
commit 202b8e34a2 ("BUG/MINOR: sample: check alloc_trash_chunk() in
conv_time_common()") introduced the NULL return.
---
src/sample.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/sample.c b/src/sample.c
index efdd8138d..ba50695df 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -2732,6 +2732,8 @@ static int sample_conv_us_ltime(const struct arg *args, struct sample *smp, void
ns = (date % 1000000) * 1000; /* us part to ns */
temp = conv_time_common(args[0].data.str.area, curr_date, ns, 1);
+ if (!temp)
+ return 0;
smp->data.u.str = *temp;
smp->data.type = SMP_T_STR;
return 1;
@@ -2755,6 +2757,8 @@ static int sample_conv_ms_ltime(const struct arg *args, struct sample *smp, void
ns = (date % 1000) * 1000000; /* ms part to ns */
temp = conv_time_common(args[0].data.str.area, curr_date, ns, 1);
+ if (!temp)
+ return 0;
smp->data.u.str = *temp;
smp->data.type = SMP_T_STR;
return 1;
@@ -2814,6 +2818,8 @@ static int sample_conv_us_utime(const struct arg *args, struct sample *smp, void
ns = (date % 1000000) * 1000; /* us part to ns */
temp = conv_time_common(args[0].data.str.area, curr_date, ns, 0);
+ if (!temp)
+ return 0;
smp->data.u.str = *temp;
smp->data.type = SMP_T_STR;
return 1;
@@ -2837,6 +2843,8 @@ static int sample_conv_ms_utime(const struct arg *args, struct sample *smp, void
ns = (date % 1000) * 1000000; /* ms part to ns */
temp = conv_time_common(args[0].data.str.area, curr_date, ns, 0);
+ if (!temp)
+ return 0;
smp->data.u.str = *temp;
smp->data.type = SMP_T_STR;
return 1;
--
2.50.1
From 829e83213130d26e7e513dcd5044b79660b6a837 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Wed, 28 Oct 2026 22:33:16 +0800
Subject: [PATCH 2/2] BUG/MINOR: debug: fix "debug dev fd" crash on unknown
address families
"debug dev fd" formats the local and peer addresses of each fd with
sa2str() and then replaces non-printable characters in the result.
sa2str() returns NULL for address families other than AF_INET, AF_INET6
and AF_UNIX, so an fd of another family, such as an inherited AF_NETLINK
socket, makes strlen(NULL) crash the process.
Only an admin in expert mode can run the command.
Let's skip the cleanup and print "?" when sa2str() fails.
This fixes GitHub issue #3546.
This must be backported to all stable versions starting with 3.1, where
commit 944a224358 ("MINOR: cli: remove non-printable characters from
'debug dev fd'") introduced the strlen() call.
---
src/debug.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/debug.c b/src/debug.c
index bc6355e12..61295f24f 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -2304,12 +2304,12 @@ static int debug_iohandler_fd(struct appctx *appctx)
port = 0;
addrstr = sa2str(&sa, port, 0);
/* cleanup the output */
- for (i = 0; i < strlen(addrstr); i++) {
+ for (i = 0; addrstr && i < strlen(addrstr); i++) {
if (iscntrl((unsigned char)addrstr[i]) || !isprint((unsigned char)addrstr[i]))
addrstr[i] = '.';
}
- chunk_appendf(&trash, " laddr=%s", addrstr);
+ chunk_appendf(&trash, " laddr=%s", addrstr ? addrstr : "?");
free(addrstr);
}
@@ -2323,11 +2323,11 @@ static int debug_iohandler_fd(struct appctx *appctx)
port = 0;
addrstr = sa2str(&sa, port, 0);
/* cleanup the output */
- for (i = 0; i < strlen(addrstr); i++) {
+ for (i = 0; addrstr && i < strlen(addrstr); i++) {
if ((iscntrl((unsigned char)addrstr[i])) || !isprint((unsigned char)addrstr[i]))
addrstr[i] = '.';
}
- chunk_appendf(&trash, " raddr=%s", addrstr);
+ chunk_appendf(&trash, " raddr=%s", addrstr ? addrstr : "?");
free(addrstr);
}
--
2.50.1