master: x86-64, struct-by-value: prevent overwrites when copying during callbacks

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

- Log -----------------------------------------------------------------
commit f484412bc4e3ebbbd2eedf83608a29aed6dc4e9c
Author: Jesse Bouwman <[email protected]>
Date:   Sun Apr 26 10:41:45 2026 -0700

    x86-64, struct-by-value: prevent overwrites when copying during callbacks
---
 src/compiler/x86-64/c-call.lisp       | 36 +++++++++++++++++++++++++++++------
 tests/alien-struct-access.c           |  6 ++++++
 tests/alien-struct-access.impure.lisp | 18 ++++++++++++++++++
 3 files changed, 54 insertions(+), 6 deletions(-)

diff --git a/src/compiler/x86-64/c-call.lisp b/src/compiler/x86-64/c-call.lisp
index 362df5a9d..bd4053c11 100644
--- a/src/compiler/x86-64/c-call.lisp
+++ b/src/compiler/x86-64/c-call.lisp
@@ -841,6 +841,34 @@ Floats are passed in integer registers."
 (defun alien-callback-accessor-form (type sp offset)
   `(deref (sap-alien (sap+ ,sp ,offset) (* ,type))))
 
+;;; Copy SIZE bytes from [SRC-REG] to [DST-REG] using SCRATCH as a temp.
+#-sb-xc-host
+(defun emit-sret-copy (size src-reg dst-reg scratch)
+  (multiple-value-bind (full-words tail) (floor size 8)
+    (loop for i below full-words
+          for off = (* i 8)
+          do (inst mov scratch (ea off src-reg))
+             (inst mov (ea off dst-reg) scratch))
+    (when (plusp tail)
+      (let ((off (* full-words 8)))
+        (flet ((copy-byte-at (k)
+                 (inst mov :byte scratch (ea (+ off k) src-reg))
+                 (inst mov :byte (ea (+ off k) dst-reg) scratch))
+               (copy-word-at (k)
+                 (inst mov :word scratch (ea (+ off k) src-reg))
+                 (inst mov :word (ea (+ off k) dst-reg) scratch))
+               (copy-dword-at (k)
+                 (inst mov :dword scratch (ea (+ off k) src-reg))
+                 (inst mov :dword (ea (+ off k) dst-reg) scratch)))
+          (ecase tail
+            (1 (copy-byte-at 0))
+            (2 (copy-word-at 0))
+            (3 (copy-word-at 0) (copy-byte-at 2))
+            (4 (copy-dword-at 0))
+            (5 (copy-dword-at 0) (copy-byte-at 4))
+            (6 (copy-dword-at 0) (copy-word-at 4))
+            (7 (copy-dword-at 0) (copy-word-at 4) (copy-byte-at 6))))))))
+
 #-sb-xc-host
 (defun alien-callback-assembler-wrapper (index result-type argument-types)
   ;; Windows x64 struct-by-value callback rules:
@@ -1127,9 +1155,7 @@ Floats are passed in integer registers."
                   ;; Retrieve saved hidden pointer (was pushed at start from RCX)
                   (inst mov rax (ea (* (+ arg-slot-count return-slot-count-aligned) n-word-bytes) rsp))
                   ;; Copy struct data from stack to hidden pointer destination
-                  (loop for off from 0 below struct-size by 8
-                        do (inst mov rdx (ea off rsp))
-                           (inst mov (ea off rax) rdx))))
+                  (emit-sret-copy struct-size rsp rax rdx)))
                ;; Small struct (<=8 bytes): just load into RAX
                (t
                 (inst mov rax [rsp])))
@@ -1139,9 +1165,7 @@ Floats are passed in integer registers."
                (large-struct-return-p
                 (let ((struct-size (sb-alien::struct-classification-size result-classification)))
                   (inst mov rax (ea (* (+ arg-slot-count return-slot-count-aligned) n-word-bytes) rsp))
-                  (loop for off from 0 below struct-size by 8
-                        do (inst mov rdx (ea off rsp))
-                           (inst mov (ea off rax) rdx))))
+                  (emit-sret-copy struct-size rsp rax rdx)))
                ;; Small struct: copy to registers based on classification
                (t
                 (let ((slots (sb-alien::struct-classification-register-slots result-classification))
diff --git a/tests/alien-struct-access.c b/tests/alien-struct-access.c
index 5a6cac7d7..8e63f285d 100644
--- a/tests/alien-struct-access.c
+++ b/tests/alien-struct-access.c
@@ -84,3 +84,9 @@ struct gp_3f  gp_3f_make  (float a, float b, float c) {
   s.a = a; s.b = b; s.c = c;
   return s;
 }
+
+/*
+ * 20-byte struct (>16B, MEMORY-class on SysV and AAPCS64), for
+ * callback trampoline test.
+ */
+typedef struct { signed char m[20]; } gp_i8x20;
diff --git a/tests/alien-struct-access.impure.lisp b/tests/alien-struct-access.impure.lisp
index 3264778e2..03596980d 100644
--- a/tests/alien-struct-access.impure.lisp
+++ b/tests/alien-struct-access.impure.lisp
@@ -213,3 +213,21 @@
                      (assert (= (slot s 'a) 1.0f0))
                      (assert (= (slot s 'b) 2.0f0))
                      (assert (= (slot s 'c) 3.0f0)))))
+
+;;; When a Lisp callback returns a struct >16 bytes to C, the ABI
+;;; passes a hidden pointer to the callback.  The callback assembler
+;;; wrapper copies Lisp's struct buffer to that pointer. Check for
+;;; overwrites.
+
+(define-alien-type nil (struct gp-i8x20 (m (array (signed 8) 20))))
+
+(with-test (:name :overwrite-cb-return-i8x20)
+  (with-guarded-struct (s 20 (struct gp-i8x20) sap)
+    (declare (ignore s))
+    (sb-alien:with-alien-callable
+        ((cb (struct gp-i8x20) ()
+           (sb-alien:with-alien ((view (struct gp-i8x20))) view)))
+      (with-fault-handler "OVER-WRITE (callback trampoline)"
+        (alien-funcall-into
+         (sb-alien:sap-alien (alien-sap cb) (function (struct gp-i8x20)))
+         sap)))))

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


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.