diff --git a/arch/x86_64/syscall_arch.h b/arch/x86_64/syscall_arch.h index 074e73c..2b082ab 100644 --- a/arch/x86_64/syscall_arch.h +++ b/arch/x86_64/syscall_arch.h @@ -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; } diff --git a/tests/syscall_test.c b/tests/syscall_test.c index d4354e5..1928096 100644 --- a/tests/syscall_test.c +++ b/tests/syscall_test.c @@ -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 / 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