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