/* * vlibc — syscall layer test (todo 1). * * Exercises the raw __syscall() entry points and the errno TCB-slot * mechanism end to end: * * 1. happy path: __syscall3(SYS_write, 1, "x", 1) emits "x" on stdout and * returns 1 (one byte written); * 2. failure path: syscall_ret(SYS_openat on a nonexistent path) returns -1 * and leaves errno == ENOENT in the calling thread's TCB slot; * 3. TCB offset mechanics: a fake TCB (a local buffer whose first word is * its own address, as the real TCB layout will have) is installed as * the FS thread pointer via SYS_arch_prctl; __errno_location() must * 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 * function, whose TLS reads would see the fake TCB. * * Not part of the library proper; compiled manually for this todo (the * tests/ + make check wiring is owned by a later todo). */ #include "../src/internal/errno.h" #include "../src/internal/syscall.h" #include "../src/internal/types.h" /* Flag constants the public will own; test-local copies. */ #define TEST_AT_FDCWD (-100) #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 static int failures; /* Write a NUL-terminated string to fd via the raw syscall layer. */ static void say(int fd, const char *s) { ssize_t n = 0; while (s[n] != '\0') { n++; } syscall_ret(__syscall3(SYS_write, fd, (long)s, (long)n)); } static void check(int cond, const char *what) { if (!cond) { say(2, "FAIL: "); say(2, what); say(2, "\n"); failures++; } } /* * A stand-in thread control block. The real TCB layout (owned by the * startup todo) will store the TCB's own address at offset 0 — the self * pointer __builtin_thread_pointer() reads through %fs:0 — and errno at * VLIBC_TCB_ERRNO_OFF. This buffer mirrors exactly those two slots. */ static unsigned long fake_tcb[32]; /* * The fake TCB must be installed over a saved copy of the real FS base: * the kernel's arch_prctl only reads or writes the current base, so the * save must happen while the real thread pointer is still installed. */ static int install_fake_tcb(unsigned long *real_fs) // NOLINT(readability-non-const-parameter) { long r; fake_tcb[0] = (unsigned long)fake_tcb; /* TCB self pointer at slot 0 */ r = syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_GET_FS, (long)real_fs)); if (r != 0) { return 1; } r = syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_SET_FS, (long)fake_tcb)); if (r != 0) { return 1; } return 0; } static int 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 * stdout; exits 0 only when the failure behaved exactly as specified. */ static int failure_scenario(void) { unsigned long real_fs = 0; long r; int saved_errno; if (install_fake_tcb(&real_fs)) { return 1; } r = syscall_ret(__syscall4(SYS_openat, TEST_AT_FDCWD, (long)"/nonexistent/vlibc", TEST_O_RDONLY | TEST_O_CLOEXEC, 0)); saved_errno = errno; if (restore_real_tcb(real_fs)) { return 1; } if (r == -1 && saved_errno == ENOENT) { say(1, "openat=-1 errno=ENOENT\n"); return 0; } say(1, "openat FAIL\n"); return 1; } int main(int argc, char **argv) { long r; unsigned long real_fs = 0; if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f') { return failure_scenario(); } /* 1. Happy path: one raw byte to stdout, byte count returned. */ r = __syscall3(SYS_write, 1, (long)"x", 1); check(r == 1, "SYS_write returned byte count"); /* 2. Point FS at the fake TCB and verify the errno slot mechanics. */ check(install_fake_tcb(&real_fs) == 0, "arch_prctl fake TCB install"); { unsigned long now_fs = 0; r = syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_GET_FS, (long)&now_fs)); check(r == 0 && now_fs == (unsigned long)fake_tcb, "arch_prctl(ARCH_GET_FS) reads fake base"); } check((char *)__errno_location() == (char *)fake_tcb + VLIBC_TCB_ERRNO_OFF, "__errno_location == fake TCB + VLIBC_TCB_ERRNO_OFF"); r = syscall_ret(__syscall4(SYS_openat, TEST_AT_FDCWD, (long)"/nonexistent/vlibc", TEST_O_RDONLY | TEST_O_CLOEXEC, 0)); check(r == -1, "SYS_openat on nonexistent path returns -1"); check(errno == ENOENT, "errno == ENOENT via TCB slot"); /* 3. Restore the real thread pointer; the fake slot keeps its value. */ check(restore_real_tcb(real_fs) == 0, "arch_prctl(ARCH_SET_FS, real TCB) restored"); 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 * state, so an errno round-trip through the real thread pointer would * corrupt the host libc. The fake-TCB path proves the offset mechanics; * the real TCB is exercised once the startup todo installs vlibc's own * thread pointer. */ if (failures > 0) { say(2, "FAILED\n"); } return failures == 0 ? 0 : 1; }