]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MINOR: fd: allow fd_write_frag_line() to run before fdtab allocation
authorFrederic Lecaille <flecaille@haproxy.com>
Thu, 5 Nov 2026 09:18:37 +0000 (10:18 +0100)
committerFrederic Lecaille <flecaille@haproxy.com>
Thu, 5 Nov 2026 15:25:05 +0000 (16:25 +0100)
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.

src/fd.c

index 3591d912b50be93accfae30cbacfe3b001b33273..1a7af18b6ed408862408dd717b4bb8cfca9b7b2f 100644 (file)
--- 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 */