/* * 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. * * 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 /* 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; } /* * 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"); /* * 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; }