From: Frederic Lecaille Date: Thu, 5 Nov 2026 09:18:37 +0000 (+0100) Subject: BUG/MINOR: fd: allow fd_write_frag_line() to run before fdtab allocation X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=a124f2089973cf6c0abd8aff5fd09131252085ac;p=haproxy.git BUG/MINOR: fd: allow fd_write_frag_line() to run before fdtab allocation With traces enabled on the command line using "-dt ssl:developer:clean", a trace event is emitted during the configuration check if an SSL bind line uses "generate-certificates". check_config_validity() calls ->prepare_bind_conf() for every bind line. For SSL, this is ssl_sock_prepare_bind_conf(). When "generate-certificates" is set, that function checks that a default certificate exists, and calls ssl_sock_choose_sni_ctx() for this. This happens long before init_pollers() is called, so fdtab is still NULL. fd_write_frag_line() reads fdtab[fd].state to take the per-fd lock and to test FD_INITIALIZED, and the process segfaults as soon as the trace is written to stderr. fd_write_frag_line() has read fdtab since 931d8b79a (v2.1), but no trace could be emitted that early at that time. The crash became possible with 105c1ca13 (v3.2-dev13), which added traces to ssl_sock_choose_sni_ctx(); ssl_sock_prepare_bind_conf() has called this function from the configuration check since b80635a7e (v3.0). At this stage the process is still single-threaded, so the per-fd lock is not needed. Skip the fdtab accesses when fdtab is not allocated yet, and call writev() directly. This should be backported to 3.2 and newer. --- diff --git a/src/fd.c b/src/fd.c index 3591d912b..1a7af18b6 100644 --- a/src/fd.c +++ b/src/fd.c @@ -970,26 +970,32 @@ ssize_t fd_write_frag_line(int fd, size_t maxlen, const struct ist pfx[], size_t /* make sure we never interleave writes and we never block. This means * we prefer to fail on collision than to block. But we don't want to * lose too many logs so we just perform a few lock attempts then give - * up. + * up. Note that fdtab may not be allocated yet during the earliest + * boot stages (before init_pollers()), where traces may already be + * emitted (e.g. via "-dt" or a "traces" section). In this case the + * process is still single-threaded, so writes cannot be interleaved + * and the per-fd lock is not needed. */ - - while (HA_ATOMIC_BTS(&fdtab[fd].state, FD_EXCL_SYSCALL_BIT)) { - if (++attempts >= 200) { - /* so that the caller knows the message couldn't be delivered */ - sent = -1; - errno = EAGAIN; - goto leave; + if (fdtab) { + while (HA_ATOMIC_BTS(&fdtab[fd].state, FD_EXCL_SYSCALL_BIT)) { + if (++attempts >= 200) { + /* so that the caller knows the message couldn't be delivered */ + sent = -1; + errno = EAGAIN; + goto leave; + } + ha_thread_relax(); } - ha_thread_relax(); - } - if (unlikely(!(fdtab[fd].state & FD_INITIALIZED))) { - HA_ATOMIC_OR(&fdtab[fd].state, FD_INITIALIZED); - if (!isatty(fd)) - fd_set_nonblock(fd); + if (unlikely(!(fdtab[fd].state & FD_INITIALIZED))) { + HA_ATOMIC_OR(&fdtab[fd].state, FD_INITIALIZED); + if (!isatty(fd)) + fd_set_nonblock(fd); + } } sent = writev(fd, iovec, vec); - HA_ATOMIC_BTR(&fdtab[fd].state, FD_EXCL_SYSCALL_BIT); + if (fdtab) + HA_ATOMIC_BTR(&fdtab[fd].state, FD_EXCL_SYSCALL_BIT); leave: /* sent > 0 if the message was delivered */