ttt

getttyent

(FreeBSD/amd64) 「ldd FILE」を実行したら、FILEが実行されてビックリした

2012-02-01 23:26:06 | デジタル・インターネット

なんとなく、「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

一瞬の間をおいて…、ウインドウが開きました。えっ?! 

20120201

何が起きたか理解できず、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)
以下省略