Rubyの、 Time.now
は最終的には clock_gettime(3)
を呼び出しているそうな。
- masterの当該箇所
Time.now
はTime.new
を無引数で呼び出しているに過ぎない- ここで、
Time#initialize
の実態はtime_init
time_init
は引数がない場合time_init_0
を呼ぶtime_init_0
は timespec 構造体のポインタを引数にrb_timespec_now
を呼ぶrb_timespec_now
でclock_gettime(3)
が使えるシステムであればそれを呼ぶ。そうでない場合gettimeofday(2)
らしい…
ここで、以下のような小さなCのコードを書く。
/* man になかったけどこの宣言がないと RTLD_NEXT が定義されなかった */ #define _GNU_SOURCE #include <time.h> #include <dlfcn.h> int clock_gettime(clockid_t clk_id, struct timespec *tp){ int (*real_clock_gettime)(clockid_t, struct timespec *); /* 本来定義されるはずだった clock_gettime 関数を持ってくることができる */ /* RTLD_NEXT はライブラリ検索順序の次の場所を示している。 super みたい */ real_clock_gettime = dlsym(RTLD_NEXT, "clock_gettime"); /* 影響範囲をなるべく狭めるよう、 CLOCK_REALTIME でないものを指定された場合は、本来の関数を呼ぶようにする */ if(clk_id == CLOCK_REALTIME) { tp->tv_sec = 0; tp->tv_nsec = 0; return 0; } else { return real_clock_gettime(clk_id, tp); } }
共有ライブラリを作る。 TLPI に書いてある通りの手順。
$ gcc -g -c -fPIC -Wall jack_clock_gettime.c $ gcc -g -shared -o libjack.so jack_clock_gettime.o $ file libjack.so libjack.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=a0e098a9ff43bf30a7260ffffb8d39abb5a2a97e, not stripped
この共有ライブラリがあると…
$ ruby -e "p Time.now" 2017-02-13 15:53:03 +0900
$ LD_PRELOAD=./libjack.so ruby -e "p Time.now" 1970-01-01 09:00:00 +0900
この通り、 Time.now
の結果をUNIXエポックにすることができた。モノによるけれど、ライブラリコールを一つ上書きことは実はそんなに難しくはないことがわかる。
LD_PRELOAD
は強力な仕組みである。本質的にこの仕組みを利用した MySQLのCVE-2016-6662 は記憶に新しい。
他にも、これは直接 LD_PRELOAD
を利用しているわけではないが、例えば valgrind においては、 free/malloc などを差し替え、それらの発行回数を数えて、メモリリークの検知に使われている 参考1 参考2 参考3 *1。
ということで、これをじゃな…
その2に続く(本当?)。
参考
- RubyKaigi 2016 - Hijacking syscalls with Ruby
- Linuxプログラミングインタフェース
- Valgrindの使い方について
- dl*族のman
- 高解像で時間を取得したり操作するのが得意なフレンズのman
*1:ということなんだろうけどコードが複雑で追いきれず、動的ロードに詳しいフレンズの情報があると嬉しい…