From 811283e683cd244ae14e77cd85a2c7d4266478eb Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 24 Feb 2021 12:36:38 -0500 Subject: [PATCH] Intercept syscall This is an attempt at an implementation to address #301. Some things worth noting: - I am not particularly confident in my reverse of the variadic C ABI. While the code appears to work for me on x86_64, I could imagine some variations between platforms that I'm not understanding. - This works to intercept the invocation of syscall as seen in test/syscalltest.sh, as long as it was compiled with -DFAKE_RANDOM - defining -DINTERCEPT_SYSCALL on non-Linux platforms should result in a compile-time error. - This does *not* work to intercept the syscall sent by `openssl rand`, for some reason I don't yet understand. Perhaps openssl has some platform-specific syscall mechanism that doesn't route them through libc's syscall() shim? --- src/Makefile | 4 ++++ src/libfaketime.c | 55 +++++++++++++++++++++++++++++++++++++++++++++ test/Makefile | 3 +++ test/syscall_test.c | 11 +++++++++ test/syscalltest.sh | 29 ++++++++++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 test/syscall_test.c create mode 100755 test/syscalltest.sh diff --git a/src/Makefile b/src/Makefile index e8c7fac..95f711e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -44,6 +44,10 @@ # FAKE_PID # - Intercept getpid() # +# INTERCEPT_SYSCALL +# - (On GNU/Linux only) intercept glibc's syscall() for known relevant syscalls. +# If enabled, this currently only works to divert the getrandom syscall. +# # FORCE_MONOTONIC_FIX # - If the test program hangs forever on # " pthread_cond_timedwait: CLOCK_MONOTONIC test diff --git a/src/libfaketime.c b/src/libfaketime.c index f86a207..856f5a5 100644 --- a/src/libfaketime.c +++ b/src/libfaketime.c @@ -43,6 +43,14 @@ #include #include #include +#ifdef INTERCEPT_SYSCALL +#ifdef __linux__ +#include +#include +#else +#error INTERCEPT_SYSCALL should only be defined on GNU/Linux systems. +#endif +#endif #include "uthash.h" @@ -230,6 +238,10 @@ static ssize_t (*real_getrandom) (void *buf, size_t buflen, unsigned static pid_t (*real_getpid) (); #endif +#ifdef INTERCEPT_SYSCALL +static long (*real_syscall) (long, ...); +#endif + static int initialized = 0; /* prototypes */ @@ -2460,6 +2472,10 @@ static void ftpl_init(void) real_getpid = dlsym(RTLD_NEXT, "getpid"); #endif +#ifdef INTERCEPT_SYSCALL + real_syscall = dlsym(RTLD_NEXT, "syscall"); +#endif + #ifdef FAKE_PTHREAD #ifdef __GLIBC__ @@ -3716,6 +3732,45 @@ pid_t getpid() { } #endif +#ifdef INTERCEPT_SYSCALL +/* see https://github.com/wolfcw/libfaketime/issues/301 */ +long syscall(long number, ...) { + va_list ap; + va_start(ap, number); +#ifdef FAKE_RANDOM + if (number == __NR_getrandom && getenv("FAKERANDOM_SEED")) { + void *buf; + size_t buflen; + unsigned int flags; + buf = va_arg(ap, void*); + buflen = va_arg(ap, size_t); + flags = va_arg(ap, unsigned int); + va_end(ap); + return getrandom(buf, buflen, flags); + } +#endif +/* + Invocations of C variadic arguments that are smaller than int are + promoted to int. For larger arguments, it's likely that they are + chopped into int-sized pieces. + + So the passthrough part of this code is attempting to reverse that + ABI so we can pass the arguments back into syscall(). + + Note that the Linux kernel appears to have baked-in 6 as the + maximum number of arguments for a syscall beyond the syscall number + itself. +*/ +#define vararg_promotion_t int +#define syscall_max_args 6 + vararg_promotion_t a[syscall_max_args]; + for (int i = 0; i < syscall_max_args; i++) + a[i] = va_arg(ap, vararg_promotion_t); + va_end(ap); + return real_syscall(number, a[0], a[1], a[2], a[3], a[4], a[5]); +} +#endif + /* * Editor modelines * diff --git a/test/Makefile b/test/Makefile index 8f56489..fbbcde3 100644 --- a/test/Makefile +++ b/test/Makefile @@ -31,6 +31,9 @@ randomtest: getrandom_test use_lib_random librandom.so getpidtest: use_lib_getpid libgetpid.so ./pidtest.sh +syscalltest: syscall_test + ./syscalltest.sh + lib%.o: lib%.c ${CC} -c -o $@ -fpic ${CFLAGS} $< diff --git a/test/syscall_test.c b/test/syscall_test.c new file mode 100644 index 0000000..cf9f9f9 --- /dev/null +++ b/test/syscall_test.c @@ -0,0 +1,11 @@ +#include +#include +#include + +int main() { + int d = 0; + long r = syscall(__NR_getrandom, &d, sizeof(d), 0); + printf("getrandom(%d, , %zd, 0) returned %ld and yielded 0x%08x\n", + __NR_getrandom, sizeof(d), r, d); + return 0; +} diff --git a/test/syscalltest.sh b/test/syscalltest.sh new file mode 100755 index 0000000..0b5a4cf --- /dev/null +++ b/test/syscalltest.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +FTPL="${FAKETIME_TESTLIB:-../src/libfaketime.so.1}" + +set -e + +error=0 +run0=$(./syscall_test) +run1=$(LD_PRELOAD="$FTPL" ./syscall_test) +run2=$(FAKERANDOM_SEED=0x0000000000000000 LD_PRELOAD="$FTPL" ./syscall_test) +run3=$(FAKERANDOM_SEED=0x0000000000000000 LD_PRELOAD="$FTPL" ./syscall_test) +run4=$(FAKERANDOM_SEED=0xDEADBEEFDEADBEEF LD_PRELOAD="$FTPL" ./syscall_test) + +if [ "$run0" = "$run1" ] ; then + error=1 + printf >&2 'test run without LD_PRELOAD matches run with LD_PRELOAD. This is very unlikely.\n' +fi +if [ "$run1" = "$run2" ] ; then + error=2 + printf >&2 'test with LD_PRELOAD but without FAKERANDOM_SEED matches run with LD_PRELOAD and FAKERANDOM_SEED. This is also very unlikely.\n' +fi +if [ "$run2" != "$run3" ]; then + error=1 + printf >&2 'test run with same seed produces different outputs.\n' +fi +if [ "$run3" = "$run4" ]; then + error=1 + printf >&2 'test runs with different seeds produce the same outputs.\n' +fi