fix(syscall): pin r10/r8 for 4- and 5-argument syscalls

This commit is contained in:
2026-09-03 18:32:44 -04:00
parent 601e1fe960
commit 2480c63fbd
2 changed files with 65 additions and 2 deletions
+5 -2
View File
@@ -75,10 +75,11 @@ static inline long __attribute__((always_inline))
__syscall4(long n, long a, long b, long c, long d)
{
unsigned long ret;
register long r10 __asm__("r10") = d;
__asm__ volatile("syscall"
: "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c), "r"(d)
: "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10)
: "rcx", "r11", "memory");
return (long)ret;
}
@@ -87,10 +88,12 @@ static inline long __attribute__((always_inline))
__syscall5(long n, long a, long b, long c, long d, long e)
{
unsigned long ret;
register long r10 __asm__("r10") = d;
register long r8 __asm__("r8") = e;
__asm__ volatile("syscall"
: "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c), "r"(d), "r"(e)
: "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10), "r"(r8)
: "rcx", "r11", "memory");
return (long)ret;
}
+60
View File
@@ -14,6 +14,9 @@
* then point exactly at fake + VLIBC_TCB_ERRNO_OFF, a failing syscall
* must land its errno in that slot, and the real thread pointer is
* restored afterwards.
* 4. register pinning: a 4-arg (SYS_rt_sigprocmask) and a 5-arg
* (SYS_ppoll) syscall whose later arguments are load-bearing must see
* them in r10/r8, where the x86_64 kernel ABI places args 4 and 5.
*
* All diagnostics go through raw SYS_write (no printf): between installing
* and restoring the fake thread pointer the test must not call any libc
@@ -32,6 +35,11 @@
#define TEST_O_RDONLY 0
#define TEST_O_CLOEXEC 0x80000
/* Constants the public <signal.h>/<poll.h> will own; test-local copies. */
#define TEST_SIG_BLOCK 0
#define TEST_SIG_SETMASK 2
#define TEST_SIGUSR1 10
/* arch_prctl codes (kernel UAPI). */
#define TEST_ARCH_SET_FS 0x1002
#define TEST_ARCH_GET_FS 0x1003
@@ -101,6 +109,54 @@ restore_real_tcb(unsigned long real_fs)
return syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_SET_FS, (long)real_fs)) != 0;
}
struct test_timespec
{
long tv_sec;
long tv_nsec;
};
/*
* 4-arg regression probe: block SIGUSR1, read the mask back through a NULL
* set, restore. The 4th argument (sigsetsize) must reach the kernel in r10;
* placed anywhere else it fails with -EINVAL.
*/
static int
probe_rt_sigprocmask(void)
{
unsigned long mask = 1UL << (TEST_SIGUSR1 - 1);
unsigned long oldmask = 0;
unsigned long readback = 0;
if (__syscall4(SYS_rt_sigprocmask, TEST_SIG_BLOCK, (long)&mask, (long)&oldmask, 8) != 0)
{
return 0;
}
if (__syscall4(SYS_rt_sigprocmask, TEST_SIG_BLOCK, 0, (long)&readback, 8) != 0)
{
return 0;
}
if (__syscall4(SYS_rt_sigprocmask, TEST_SIG_SETMASK, (long)&oldmask, 0, 8) != 0)
{
return 0;
}
return (readback & (1UL << (TEST_SIGUSR1 - 1))) != 0;
}
/*
* 5-arg regression probe: ppoll with no fds and a zero timeout must return
* 0. The 4th argument (sigmask) must reach r10 and the 5th (sigsetsize) r8;
* misplaced, the kernel reads a pointer value where the set size belongs and
* answers -EINVAL.
*/
static int
probe_ppoll(void)
{
struct test_timespec ts = {0, 0};
unsigned long mask = 0;
return __syscall5(SYS_ppoll, 0, 0, (long)&ts, (long)&mask, 8) == 0;
}
/*
* Failure scenario (-f): SYS_openat on a nonexistent path must return -1
* and leave errno == ENOENT in the TCB slot. Prints the observed result to
@@ -173,6 +229,10 @@ main(int argc, char **argv)
check(*(int *)((char *)fake_tcb + VLIBC_TCB_ERRNO_OFF) == ENOENT,
"fake TCB errno slot retains ENOENT after restore");
/* 4. Register pinning for 4- and 5-argument syscalls (r10/r8). */
check(probe_rt_sigprocmask(), "rt_sigprocmask 4-arg r10 pinning");
check(probe_ppoll(), "ppoll 5-arg r10/r8 pinning");
/*
* VLIBC_TCB_ERRNO_OFF addresses slot 1 of whichever TCB the FS thread
* pointer selects; under the host libc that slot is its private TLS