From 618c6892468a44f7e0e32197684fefe4a9565387 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Fri, 10 Apr 2026 16:13:07 +0200 Subject: [PATCH] runtime: fix Darwin syscall return value truncation and lost arguments Go 1.26 replaced the individual Darwin syscall entry points (syscall, syscallX, syscallPtr, syscall6, syscall6X) with two variadic functions: syscalln and rawsyscalln. The old wrappers now have Go bodies that call these, then use errno/errnoX/errnoPtr to interpret the result. This caused two bugs in our rawsyscalln implementation: 1. Return value truncation: We used call_syscall/call_syscall6 which return int32, truncating 64-bit results (pointers from fdopendir, offsets from lseek, addresses from mmap). For example, a DIR* pointer returned by fdopendir would lose its upper 32 bits on arm64, causing SIGSEGV when later accessed. 2. Lost 4th argument: The case 4/5/6 fallthrough had 'a3 = args[3]' instead of 'a4 = args[3]', and a3 was immediately overwritten by 'a1, a2, a3 = args[0], args[1], args[2]'. This lost the 4th syscall argument entirely, breaking pread (offset=0) and causing wrong data to be read from files. Fix by using call_syscallX/call_syscall6X (returning full uintptr), always reading errno (letting the Go wrappers decide what to do with it), and correcting the argument assignment. --- src/runtime/os_darwin_go126.go | 41 +++++++++++++++------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/runtime/os_darwin_go126.go b/src/runtime/os_darwin_go126.go index 1845fa513..d676482ca 100644 --- a/src/runtime/os_darwin_go126.go +++ b/src/runtime/os_darwin_go126.go @@ -2,6 +2,17 @@ package runtime +// Go 1.26 replaced the individual Darwin syscall functions (syscall, syscallX, +// syscallPtr, syscall6, syscall6X) with two variadic entry points: syscalln and +// rawsyscalln. All the old wrappers now have Go bodies that call these and then +// interpret the result (errno, errnoX, errnoPtr). Because the callers decide +// how to check for errors, we must: +// - always return the full register-width result (use call_syscallX / +// call_syscall6X which return uintptr, not call_syscall / call_syscall6 +// which truncate to int32), and +// - always read errno so that the Go wrappers have the value available when +// the error condition is met. + // syscall_syscalln is a wrapper around the libc call with variable arguments. // //go:nosplit @@ -30,7 +41,9 @@ func syscall_rawsyscalln(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { a1 = args[0] fallthrough case 0: - return runtime_syscall(fn, a1, a2, a3) + r1 = call_syscallX(fn, a1, a2, a3) + err = uintptr(*libc_errno_location()) + return case 6: a6 = args[5] @@ -39,31 +52,13 @@ func syscall_rawsyscalln(fn uintptr, args ...uintptr) (r1, r2, err uintptr) { a5 = args[4] fallthrough case 4: - a3 = args[3] + a4 = args[3] a1, a2, a3 = args[0], args[1], args[2] - return runtime_syscall6(fn, a1, a2, a3, a4, a5, a6) + r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6) + err = uintptr(*libc_errno_location()) + return } panic("syscall args not handled") } - -func runtime_syscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { - result := call_syscall(fn, a1, a2, a3) - r1 = uintptr(result) - if result == -1 { - // Syscall returns -1 on failure. - err = uintptr(*libc_errno_location()) - } - return -} - -func runtime_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { - result := call_syscall6(fn, a1, a2, a3, a4, a5, a6) - r1 = uintptr(result) - if result == -1 { - // Syscall returns -1 on failure. - err = uintptr(*libc_errno_location()) - } - return -}