]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: mux-fcgi: don't return a freed tasklet from fcgi_deferred_shut()
authorWilly Tarreau <w@1wt.eu>
Tue, 6 Oct 2026 20:42:00 +0000 (22:42 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 12 Oct 2026 12:31:04 +0000 (14:31 +0200)
Since 3.2 and commit 8f70f9c04 ("MEDIUM: mux_fcgi: Make sure we return
the tasklet from fcgi_deferred_shut"), fcgi_deferred_shut() returns its
tasklet to the scheduler. But when the deferred shutdown concerns an
orphaned stream, fcgi_strm_destroy() is called, which frees the stream's
shut_tl tasklet, which is precisely the one being run. The scheduler then
clears TASK_RUNNING in the freed tasklet's state, which is a
use-after-free write (and the connection may even have been released).

This is the same issue as the one fixed in mux-h2 by commit "BUG/MEDIUM:
mux-h2: don't return a freed tasklet from h2_deferred_shut()", where it
was reproduced with -dMuaf. Here it's only based on code analysis. It
requires that the FCGI_ABORT or the end of stream record could not be
sent when the stream was shut because the mux buffer was full, and that
the stream detaches before the deferred shutdown is performed, so it's
rare and only the FCGI server side (trusted) is involved.

The fix simply consists in returning NULL once the stream was destroyed.
It must be backported to 3.2.

src/mux_fcgi.c

index c01c5f0578f331bf712f6abf53f6c58e8287a118..cc92ac8ef7ef5177a86a0bc0d9db0ca185dd54b3 100644 (file)
@@ -4005,7 +4005,9 @@ struct task *fcgi_deferred_shut(struct task *t, void *ctx, unsigned int state)
                LIST_DEL_INIT(&fstrm->send_list);
 
                if (!fcgi_strm_sc(fstrm)) {
+                       /* note: this frees the tasklet as well */
                        fcgi_strm_destroy(fstrm);
+                       t = NULL;
                        if (fcgi_conn_is_dead(fconn))
                                fcgi_release(fconn);
                }