wolfcw / libfaketime

libfaketime modifies the system time for a single application
https://github.com/wolfcw/libfaketime
GNU General Public License v2.0
2.71k stars 325 forks source link

Libfaketime not working on MacOS 12 Monterey #357

Closed ronrother closed 2 years ago

ronrother commented 2 years ago

Hi @wolfcw

I'm having trouble getting libfaketime to work with MacOS 12, possibly due to changes in how dyld works. I am using an M1 mac, but it doesn't seem to be related to the architecture. Repro (from libfaketime repo root):

make

cat <<EOF > monterepro.c
#include <stdio.h>
#include <time.h>
#include <dlfcn.h>

static int (*next_clock_gettime) (clockid_t clk_id, struct timespec *tp);

int main()
{
    // This will print 2021
    struct timespec now;
    clock_gettime(CLOCK_REALTIME, &now);
    printf(ctime(&now));

    // clock_gettime was loaded from /usr/lib/system/libsystem_c.dylib
    Dl_info dl_info;
    dladdr((void *)clock_gettime, &dl_info);
    printf("%s\n", dl_info.dli_fname);

    // Libfaketime itself works -- the block below prints 2029
    void* handle = dlopen("./src/libfaketime.1.dylib", RTLD_LAZY);
    next_clock_gettime = dlsym(handle, "clock_gettime");
    next_clock_gettime(CLOCK_REALTIME, &now);
    printf(ctime(&now));
}
EOF

clang -o monterepro monterepro.c

DYLD_INSERT_LIBRARIES=./src/libfaketime.1.dylib DYLD_FORCE_FLAT_NAMESPACE=YES FAKETIME='@2029-08-14 21:59:54' ./monterepro

At the end of the output, you should see:

Thu Dec  9 xx:xx:xx 2021
/usr/lib/system/libsystem_c.dylib
Tue Aug 14 21:59:54 2029

It looks like not matter what I do, the clock functions are loaded from libsystem_c.dylib. Running with DYLD_PRINT_LIBRARIES=YES shows that libfaketime is indeed being loaded:

>  libfaketime(master): DYLD_INSERT_LIBRARIES=./src/libfaketime.1.dylib DYLD_PRINT_LIBRARIES=YES DYLD_FORCE_FLAT_NAMESPACE=YES FAKETIME='@2029-08-14 21:59:54' ./monterepro
dyld[70537]: <31AA8C31-5441-300A-8FA6-F2000B296E25> /Users/ronrother/libfaketime/monterepro
dyld[70537]: <9905DE48-8EA9-3106-966C-E80EC7BA33B9> /Users/ronrother/libfaketime/src/libfaketime.1.dylib
...

But DYLD_PRINT_BINDINGS suggests libfaketime is not taking precedence:

>  libfaketime(master): DYLD_INSERT_LIBRARIES=./src/libfaketime.1.dylib DYLD_PRINT_BINDINGS=YES DYLD_FORCE_FLAT_NAMESPACE=YES FAKETIME='@2029-08-14 21:59:54' ./monterepro
...
dyld[77121]: <monterepro/bind#0> -> 0x1bc324d3c (libsystem_c.dylib/_clock_gettime)
...

This style of interposition still works with functions from other libraries, but doesn't seem to work with functions from libsystem.

For the record: I had success using this approach: http://toves.freeshell.org/interpose/. By copying this DYLD_INTERPOSE macro from https://opensource.apple.com/source/dyld/dyld-97.1/include/mach-o/dyld-interposing.h.auto.html, renaming clock_gettime in libfaketime.c to interposed_clock_gettime, applying DYLD_INTERPOSE to it and calling clock_gettime directly instead of using real_clock_gettime, I got it to work (as a POC):

...
#define DYLD_INTERPOSE(_replacment,_replacee) \
   __attribute__((used)) static struct{ const void* replacment; const void* replacee; } _interpose_##_replacee \
            __attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacment, (const void*)(unsigned long)&_replacee };

# renamed from clock_gettime
int interposed_clock_gettime(clockid_t clk_id, struct timespec *tp)
{
...
DONT_FAKE_TIME(result = (*clock_gettime)(clk_id, tp));
...
}

DYLD_INTERPOSE(interposed_clock_gettime, clock_gettime);

But it looks like this approach would require a large refactor, and I'm not sure how far it would be in terms of backwards-compatibility.

wolfcw commented 2 years ago

Basic functionality should work on Monterey meanwhile again. I'm open for any improvements.