master: Implement a SIGSEGV mitigation strategy for tls-load-indirect

snuglas via Sbcl-commits <[email protected]>
Newsgroups gmane.lisp.steel-bank.cvs
Message-ID <[email protected]>
The branch "master" has been updated in SBCL:
       via  f368a022c003221f206ddc86239278e0c5e3f723 (commit)
      from  737f78096654351c67897079b61b75c2749fc31e (commit)

- Log -----------------------------------------------------------------
commit f368a022c003221f206ddc86239278e0c5e3f723
Author: Douglas Katzman <[email protected]>
Date:   Sun Apr 5 22:42:20 2026 -0400

    Implement a SIGSEGV mitigation strategy for tls-load-indirect
    
    A certain local test was _incorrectly_ reporting use-after-free under ASan
    where the alleged bug was in the TLS trap handler, and it was undebuggable
    because the sigcontext itself was the supposed problem.
    I gave up, and implemented something hinted at with the initial commit of
    the :tls-load-indirect feature, which is that the indirection cells can
    be more-or-less prepopulated at thread startup.
    My ASan test failure rate went from 10% down to 0 failures in 10,000 runs.
---
 src/code/late-globaldb.lisp | 20 ++++++++++++++++++++
 src/runtime/os-common.c     | 13 +++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/src/code/late-globaldb.lisp b/src/code/late-globaldb.lisp
index c0e9aed86..eee9c8575 100644
--- a/src/code/late-globaldb.lisp
+++ b/src/code/late-globaldb.lisp
@@ -81,6 +81,10 @@
   ;; cells contain NO-TLS-VALUE which ordinarily causes SET to affect SYMBOL-GLOBAL-VALUE.
   ;; So we have to store directly into offsets off the primitive thread.
   ;; See %SET-SYMBOL-VALUE-IN-THREAD for comparison.
+  ;; (On x86-64, the SET vops understands that for a symbol which is always-thread-local
+  ;; it should store into the TLS bypassing the test for NO-TLS-VALUE. However, that is
+  ;; currently not a mandatory behavior, but rather an optimization, and not all the
+  ;; backends behave as desired)
   #+sb-thread
   (macrolet ((expand ()
                `(setf (sap-ref-lispobj sap ,(info :variable :wired-tls '*current-thread*))
@@ -95,6 +99,22 @@
                                        ,(if (equal form '(sb-kernel:make-unbound-marker))
                                             'ubm form)))))))
     (let ((sap (current-thread-sap)) (ubm (make-unbound-marker))) (expand)))
+  ;; Some applications can't tolerate unexpected SIGSEGV. Even ones that ordinarily could
+  ;; may exhibit sporadic crashes under the LLVM interceptors which change the sa_flags in
+  ;; struct sigaction before passing along the syscall argument. (How is that reasonable?)
+  #+tls-load-indirect
+  (when (= (extern-alien "enable_tls_indirection_preinit" char) 1)
+    (do ((symbolmap (int-sap (ash sb-vm::*tls-symbol-map* sb-vm:n-fixnum-tag-bits)))
+         (i (- (symbol-tls-index '*package*) 8) (+ i 16)) ; step by 2 words
+         (end (- (ash sb-vm::*free-tls-index* sb-vm:n-fixnum-tag-bits) 8)))
+        ((>= i end))
+      (let ((indirection-word (sap-ref-word (current-thread-sap) i)))
+        (when (= indirection-word sb-vm:no-tls-value-marker) ; now must point it to the symbol
+          ;; (could AVER that no-tls-value is stored as the value, but that would just crash)
+          (let ((symbol (sap-ref-lispobj symbolmap (ash i -1))))
+            ;; If no symbol, the index is unused (or else there's an unavoidable race)
+            (unless (= (get-lisp-obj-address symbol) sb-vm:no-tls-value-marker)
+              (setf (sap-ref-lispobj (sb-thread:current-thread-sap) i) symbol)))))))
   thread)
 
 (eval-when (:compile-toplevel)
diff --git a/src/runtime/os-common.c b/src/runtime/os-common.c
index 0204189a9..262e645e3 100644
--- a/src/runtime/os-common.c
+++ b/src/runtime/os-common.c
@@ -48,6 +48,19 @@ os_vm_size_t os_vm_page_size = BACKEND_PAGE_BYTES;
 /* Expose to Lisp the value of the preprocessor define. Don't touch! */
 int install_sig_memory_fault_handler = INSTALL_SIG_MEMORY_FAULT_HANDLER;
 
+#ifdef ADDRESS_SANITIZER
+/* This trick prevents ASan from reporting false positives.
+ * Not only could I not get spurious errors to go away by sprinkling NO_SANITIZE_ADDRESS
+ * all over, even if I could, it was too invasive for my liking. The difficulty stemmed
+ * from the fact the sanitizer thought that sigcontexts from the kernel aren't usable
+ * (it said that reading them constituted a use-after-free bug) and so I couldn't so much
+ * as call arch_get_bad_addr() or take *os_context_pc_addr(context) without encountering
+ * recursive errors. Whereas this fixed it with fairly localized intervention */
+char enable_tls_indirection_preinit = 1;
+#else
+char enable_tls_indirection_preinit = 0;
+#endif
+
 /* Except for os_zero, these routines are only called by Lisp code.
  * These routines may also be replaced by os-dependent versions
  * instead. */

-----------------------------------------------------------------------


hooks/post-receive
-- 
SBCL
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.