Posted to tcl by stevel at Wed Jul 29 01:38:55 GMT 2026view raw
- What the problem was
-
- TclTLS freed an OpenSSL data structure at exit while TLS channels were still
- using it.
-
- Concretely, in TlsLibInit() the extension registered a Tcl exit handler:
-
- Tcl_CreateExitHandler(TlsLibShutdown, NULL);
-
- and TlsLibShutdown() BIO_cleanup() BIO_meth_free(BioMethods). BioMethods
- is the custom BIO_METHOD the vtable of function pointers (read, write, ctrl,
- destroy) that every TclTLS BIO points at to route OpenSSL's I/O through Tcl
- channels.
-
- The fatal assumption was that exit handlers run last. They don't. In Tcl 9's
- generic/tclEvent.c, both exit paths run exit handlers first and close channels
- afterwards:
-
- - Tcl_Exit() InvokeExitHandlers() (line 981) FinalizeThread() (996)
- TclFinalizeIOSubsystem() (1413)
- - Tcl_Finalize() InvokeExitHandlers() (1201) Tcl_FinalizeThread() (1223)
- same
-
- So on exit:
-
- 1. TlsLibShutdown frees the vtable.
- 2. TclFinalizeIOSubsystem then walks every channel still open and closes it.
- 3. Closing your live XMPP socket ran TlsClose2Proc TlsCloseProc, which
- called into the BIO and BIO_ctrl() dereferences b->method->ctrl to dispatch.
- That vtable was freed memory. Jump through a garbage pointer segfault.
-
- That's why it only happened on exit, and only with a connection still up:
- jabberlib's XMPP socket was still open, so there was a live BIO pointing at a
- dead vtable. Close everything first and there's nothing left to trip over.
- It's also heap-dependent if the freed block hadn't been reused yet, the
- stale pointers still read as valid and the process exited cleanly, which is
- exactly why it looked intermittent.
Add a comment