なんとなく、「ldd /usr/local/bin/flash-player-properties」というコマンドを実行してみたら
% ldd /usr/local/bin/flash-player-properties
/usr/local/bin/flash-player-properties:
(process:28220): Gtk-WARNING **: Locale not supported by C library.
Using the fallback 'C' locale.
Gtk-Message: Failed to load module "canberra-gtk-module": libcanberra-gtk-module.so: cannot open shared object file: No such file or directory
Gtk-Message: Failed to load module "gnomesegvhandler": libgnomesegvhandler.so: cannot open shared object file: No such file or directory
一瞬の間をおいて…、ウインドウが開きました。えっ?!
何が起きたか理解できず、DISPLAY環境変数を消して、もう一度…
% unsetenv DISPLAY
% ldd /usr/local/bin/flash-player-properties
/usr/local/bin/flash-player-properties:
(process:29797): Gtk-WARNING **: Locale not supported by C library.
Using the fallback 'C' locale.
(flash-player-properties:29797): Gtk-WARNING **: cannot open display:
/usr/local/bin/flash-player-properties: exit status 1
う~ん、これ、どうみても、/usr/local/bin/flash-player-propertiesを実行してますよね。lddは、共有ライブラリを表示するコマンドなのに、どうして、コマンドそのものを実行しちゃうんですか?という疑問。
どうも、lddの対象ファイルがLinuxバイナリのときに、この珍現象が起きるような雰囲気。
% file /usr/local/bin/flash-player-properties
/usr/local/bin/flash-player-properties: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.9, stripped
ためしに、かなり古い、FreeBSD 7.2-STABLE (32bit版)で同様なことをやってみると
% file less
less: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.9, dynamically linked (uses shared libs), stripped
% ldd less
less:
libncursesw.so.5 => /lib/libncursesw.so.5 (0x4808b000)
libc.so.6 => /lib/libc.so.6 (0x480bb000)
libdl.so.2 => /lib/libdl.so.2 (0x48233000)
libtinfo.so.5 => /lib/libtinfo.so.5 (0x48238000)
/lib/ld-linux.so.2 (0x48067000)
こちらはまったく問題なし。
いろいろ試行錯誤してみて、わかりました。整理してみると、こんな感じ。
- FreeBSD 8.2-STABLE amd64(64ビット版)、9.0-RELEASE amd64(64ビット版)などでは、
- 32bitのLinuxバイナリに対して、lddコマンドを実行したとき、
- Linuxバイナリそのものが実行されてしまう
問題ないケース
- 32bit版のFreeBSDでは、コマンドが実行されてしまうことはない。正常に、lddが動く。
- たしか64bit版のLinuxバイナリは、そもそもFreeBSDで実行できないんでしたっけ? 64bitバイナリの場合は、「ld ホゲ64」しても、実行されない(できない?)。
☆
もう少し調べてみる。
ktrace ldd /usr/local/bin/flash-player-properties
してみると、確かにforkしてる。
29828 ldd RET sigprocmask 0
29828 ldd CALL open(0x7fffffffe524,O_RDONLY,<unused>0)
29828 ldd NAMI "/usr/local/bin/flash-player-properties"
29828 ldd RET open 3
29828 ldd CALL read(0x3,0x7fffffffe0f0,0x40)
省略
29828 ldd CALL munmap(0x800888000,0x178000)
29828 ldd RET munmap 0
29828 ldd CALL fork
29828 ldd RET fork 29829/0x7485
29828 ldd CALL wait4(0xffffffff,0x7fffffffe0e8,<invalid>0,0)
29828 ldd RET wait4 29829/0x7485
29828 ldd CALL sigprocmask(SIG_BLOCK,0x8006393a0,0x7fffffffe0b0)
29828 ldd RET sigprocmask 0
29828 ldd CALL sigprocmask(SIG_SETMASK,0x8006393b0,0)
29828 ldd RET sigprocmask 0
29828 ldd CALL sigprocmask(SIG_BLOCK,0x8006393a0,0x7fffffffe060)
29828 ldd RET sigprocmask 0
29828 ldd CALL sigprocmask(SIG_SETMASK,0x8006393b0,0)
29828 ldd RET sigprocmask 0
29828 ldd CALL exit(0x1)
なぜforkしているのか?lddにバッファオーバーフローの脆弱性でもあって、コードが送り込まれているのか?と不安になりつつ、一応、lddのソースコードを調べてみたら、納得。
% grep fork /usr/src/usr.bin/ldd/*
/usr/src/usr.bin/ldd/ldd.c: switch (fork()) {
/usr/src/usr.bin/ldd/ldd.c: err(1, "fork");
/usr/src/usr.bin/ldd/ldd.c: switch (fork()) {
/usr/src/usr.bin/ldd/ldd.c: err(1, "fork");
lddの中で、forkしてる。
よ~く見てみると、へーそうだったんだ!と、おもしろいことに気がつく。
/* ld.so magic */
setenv(LD_ "TRACE_LOADED_OBJECTS", "yes", 1);
if (fmt1 != NULL)
setenv(LD_ "TRACE_LOADED_OBJECTS_FMT1", fmt1, 1);
if (fmt2 != NULL)
setenv(LD_ "TRACE_LOADED_OBJECTS_FMT2", fmt2, 1);
setenv(LD_ "TRACE_LOADED_OBJECTS_PROGNAME", *argv, 1);
if (aflag)
setenv(LD_ "TRACE_LOADED_OBJECTS_ALL", "1", 1);
else if (fmt1 == NULL && fmt2 == NULL)
/* Default formats */
printf("%s:\n", *argv);
fflush(stdout);
switch (fork()) {
case -1:
err(1, "fork");
break;
default:
if (wait(&status) < 0) {
warn("wait");
rval |= 1;
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "%s: signal %d\n", *argv,
WTERMSIG(status));
rval |= 1;
} else if (WIFEXITED(status) &&
WEXITSTATUS(status) != 0) {
fprintf(stderr, "%s: exit status %d\n", *argv,
WEXITSTATUS(status));
rval |= 1;
}
break;
case 0:
if (is_shlib == 0) {
execl(*argv, *argv, (char *)NULL);
warn("%s", *argv);
} else {
dlopen(*argv, RTLD_TRACE);
warnx("%s: %s", *argv, dlerror());
}
_exit(1);
}
}
return rval;
}
man ld.soしてみると
LD_TRACE_LOADED_OBJECTS
When set to a nonempty string, causes ld-elf.so.1 to
exit after loading the shared objects and printing a
summary which includes the absolute pathnames of all
objects, to standard output.
そうだったんですね。
てっきり、lddが、バイナリファイルの中を読んで共有ファイルをリストアップしてたのかと思ったいたのですが、実際にはld.soがリストアップしていて、lddは環境変数LD_TRACE_LOADED_OBJECTSをセットしているだけでした。
というわけで、試してみると
% env LD_TRACE_LOADED_OBJECTS=yes /usr/local/bin/emacs
libgtk-x11-2.0.so.0 => /usr/local/lib/libgtk-x11-2.0.so.0 (0x8007cf000)
libgdk-x11-2.0.so.0 => /usr/local/lib/libgdk-x11-2.0.so.0 (0x800ce8000)
libatk-1.0.so.0 => /usr/local/lib/libatk-1.0.so.0 (0x800e9b000)
libgdk_pixbuf-2.0.so.0 => /usr/local/lib/libgdk_pixbuf-2.0.so.0 (0x800fbc000)
libpangocairo-1.0.so.0 => /usr/local/lib/libpangocairo-1.0.so.0 (0x8010da000)
libXext.so.6 => /usr/local/lib/libXext.so.6 (0x8011e7000)
libXrender.so.1 => /usr/local/lib/libXrender.so.1 (0x8012f9000)
libXinerama.so.1 => /usr/local/lib/libXinerama.so.1 (0x801402000)
libXi.so.6 => /usr/local/lib/libXi.so.6 (0x801504000)
略
という感じで、lddを使わずに、共有ライブラリの一覧を出せました。
CentOS 6でも試してみましたが、このへんの仕組みは、まったく同じですね。
% env LD_TRACE_LOADED_OBJECTS=yes /usr/bin/less
linux-vdso.so.1 => (0x00007fff70bff000)
libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007f6f24a4d000)
libpcre.so.0 => /lib64/libpcre.so.0 (0x00007f6f24821000)
libc.so.6 => /lib64/libc.so.6 (0x00007f6f24480000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6f24c6e000)
☆
結局、64bit版のFreeBSDでは、Linuxバイナリを実行する仕組み(Linuxulator)に不具合があって、Linuxのld-linux.soに、LD_TRACE_LOADED_OBJECTSをうまく渡せていない、ってことでしょうかねぇ???
とりあえず、こんな感じで、もともとやりたかったことは達成できましたが…、この不具合は、セキュリティホールに近いものじゃないですかね。
% /usr/compat/linux/bin/bash
bash-3.2$ ldd /usr/local/bin/flash-player-properties
libgtk-x11-2.0.so.0 => /usr/lib/libgtk-x11-2.0.so.0 (0x280d8000)
libgdk-x11-2.0.so.0 => /usr/lib/libgdk-x11-2.0.so.0 (0x284bf000)
libatk-1.0.so.0 => /usr/lib/libatk-1.0.so.0 (0x28554000)
libpangoft2-1.0.so.0 => /usr/lib/libpangoft2-1.0.so.0 (0x28571000)
libgdk_pixbuf-2.0.so.0 => /usr/lib/libgdk_pixbuf-2.0.so.0 (0x2859a000)
libpangocairo-1.0.so.0 => /usr/lib/libpangocairo-1.0.so.0 (0x285b7000)
以下省略