6

Ubuntu Lucid x86_64で、大量の仮想ホスティング(10,000を超える仮想ホスト)用にchrootされたphp-cgiを実行することを目的としたプロジェクトがあり、各仮想ホストには独自のchrootがあります。

/ dev / null、/ dev / zero、ロケール、アイコンなど、chrootの外部で実行されていると考えているphpモジュールが必要とする可能性のあるものについては、各chroot内に必要な環境を作成しないようにします。

目標は、php-cgiをchroot内で実行することですが、それらのファイルが(ほとんどの場合)読み取り専用モードで許可リスト(/ dev /)で開かれている限り、chroot外のファイルにアクセスできるようにします。ログ、/ dev / zero、/ dev / null、ロケールへのパス...)

明らかな方法は、chrootの外部で信頼できるopen()パスをフックしてリダイレクトできるカーネルモジュールを作成する(または存在する場合は使用する)ようです。しかし、私はそれが最も簡単な方法ではないと思います:

  • カーネルモジュールを実行したことがないので、難易度を正しく見積もることができません。
  • ファイルを「開く」(開く、接続する、mmapなど)フックする複数のシステムコールがあるようですが、ファイルを開くことに関連するすべてに共通のカーネル関数があると思います。

プラットフォームを最新の安定したPHPリリースに更新するたびに必要な作業量を最小限に抑えるために(したがって、アップストリームのPHPリリースからより頻繁かつ迅速に更新するために)、phpまたはそのモジュールへのパッチの数を最小限に抑えたいので、 PHPの動作に外部からパッチを適用する方が良いと思います(特定の設定があるため、PHPにパッチを適用してアップストリームにパッチを提案することは関係ありません)。

代わりに、私は現在ユーザーランドの解決策を試しています。LD_PRELOADを使用してlibc関数をフックします。これはほとんどの場合うまく機能し、実装が非常に高速ですが、単独では解決できない問題が発生しました。(アイデアは、chrootの外部で実行されているデーモンと通信し、ioctl SENDFDおよびRECVFDを使用してデーモンからファイル記述子を取得することです)。

(最初にopenlog()を使用せずに)syslog()を呼び出すと、syslog()はconnect()を呼び出してファイルを開きます

例:

folays@phenix:~/ldpreload$ strace logger test 2>&1 | grep connect
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(1, {sa_family=AF_FILE, path="/dev/log"}, 110) = 0

これまでのところ、libcのconnect()関数をフックしようとしましたが、成功しませんでした。また、プリロードライブラリの_init()関数内のdlopen()にいくつかのフラグを設定して、それらのいくつかがこの機能を実行できるかどうかをテストしようとしましたが、成功しませんでした

これが私のプリロードライブラリの関連コードです:

void __attribute__((constructor)) my_init(void)
{
  printf("INIT preloadz %s\n", __progname);
  dlopen(getenv("LD_PRELOAD"), RTLD_NOLOAD | RTLD_DEEPBIND | RTLD_GLOBAL |
                               RTLD_NOW);
}

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
  printf("HOOKED connect\n");
  int (*f)() = dlsym(RTLD_NEXT, "connect");
  int ret = f(sockfd, addr, addrlen);
  return ret;
}

int __connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
  printf("HOOKED __connect\n");
  int (*f)() = dlsym(RTLD_NEXT, "connect");
  int ret = f(sockfd, addr, addrlen);
  return ret;
}

しかし、libcのconnect()関数はまだ私のものよりも優先されます:

folays@phenix:~/ldpreload$ LD_PRELOAD=./lib-preload.so logger test
INIT preloadz logger
[...] no lines with "HOOKED connect..." [...]
folays@phenix:~/ldpreload$

syslog()のコード(apt-get source libc6、glibc-2.13 / misc / syslog.c)を見ると、openlog_internalを呼び出しているようで、misc / syslog.cの386行目で__connect()を呼び出しています。

            if (LogFile != -1 && !connected)
            {
                    int old_errno = errno;
                    if (__connect(LogFile, &SyslogAddr, sizeof(SyslogAddr))
                        == -1)
                    {

objdumpは、libcの動的シンボルテーブルでconnectと__connectを表示します。

folays@phenix:~/ldpreload$ objdump -T /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
00000000000e6d00  w   DF .text  000000000000005e  GLIBC_2.2.5 connect
00000000000e6d00  w   DF .text  000000000000005e  GLIBC_2.2.5 __connect

しかし、動的再配置エントリに接続シンボルがないため、openlog_internal()で使用されるconnect()を正常にオーバーライドできない理由を説明していると思います。おそらく、動的シンボル再配置を使用せず、おそらく__connect()のアドレスを持っています。ハードで機能します(相対的な-fPICオフセット?)。

folays@phenix:~/ldpreload$ objdump -R /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
folays@phenix:~/ldpreload$ 

connectは__connectの弱いエイリアスです:

 eglibc-2.13/socket/connect.c:weak_alias (__connect, connect)

gdbは、libcのlibc接続シンボルでブレークポイントを設定できます。

folays@phenix:~/ldpreload$ gdb logger
(gdb) b connect
Breakpoint 1 at 0x400dc8
(gdb) r test
Starting program: /usr/bin/logger 

Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
82      ../sysdeps/unix/syscall-template.S: No such file or directory.
        in ../sysdeps/unix/syscall-template.S
(gdb) c 2
Will ignore next crossing of breakpoint 1.  Continuing.

Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
82      in ../sysdeps/unix/syscall-template.S
(gdb) bt
#0  connect () at ../sysdeps/unix/syscall-template.S:82
#1  0x00007ffff7b28974 in openlog_internal (ident=<value optimized out>, logstat=<value optimized out>, logfac=<value optimized out>) at ../misc/syslog.c:386
#2  0x00007ffff7b29187 in __vsyslog_chk (pri=<value optimized out>, flag=1, fmt=0x40198e "%s", ap=0x7fffffffdd40) at ../misc/syslog.c:274
#3  0x00007ffff7b293af in __syslog_chk (pri=<value optimized out>, flag=<value optimized out>, fmt=<value optimized out>) at ../misc/syslog.c:131

もちろん、自分でopenlog()を実行することで、この特定の問題を完全にスキップすることもできますが、他のいくつかの関数でも同じタイプの問題が発生すると思います。

openlog_internalが動的シンボル再配置を使用して__connect()を呼び出さない理由、および単純なLD_PRELOADメカニズムを使用してこの__connect()呼び出しをフックすることさえ可能かどうか、私は本当に理解していません。

私がそれをどのように行うことができるかを見る他の方法:

  • dlopenを使用してLD_PRELOADからlibc.soをロードし、dlsym()を使用してlibcの__connectのアドレスを取得してから、関数にパッチを適用して(ASMに関して)フックを機能させます。それは本当にやり過ぎでエラーが発生しやすいようです。
  • PHP用に変更されたカスタムlibcを使用して、これらの問題をソースで直接修正します(open / connect / mmap関数...)
  • LKMをコーディングして、ファイルアクセスを必要な場所にリダイレクトします。長所:ioctl(SENDFD)の必要はなく、chrootの外部にデーモンもありません。

可能であれば、openlog_internalによって発行された__connect()の呼び出し、提案、またはsyscallのフックとリダイレクトに関連するカーネルドキュメントへのリンクをフックする方法を学びたいと思います。

「hooksyscalls」に関連する私のグーグル検索では、LSMへの参照がたくさん見つかりましたが、「yes」または「no」に答えるACLのみが許可され、open()パスのリダイレクトは許可されないようです。

読んでくれてありがとう。

4

1 に答える 1

3

大幅に変更された独自の libc をビルドしないと、これは絶対に不可能LD_PRELOADです。その場合は、リダイレクト ハックを直接内部に配置することもできます。open、などへの呼び出しが必ずしもあるとは限りconnectません。代わりに、ライブラリの作成時にバインドされた同様の隠し関数 (動的に再バインド可能ではない) への呼び出し、またはインライン syscall の呼び出しが存在する可能性があり、これはもちろんバージョンによって予期せずに変化する可能性があります。

オプションは、カーネルモジュールか、おそらくptrace「chroot」内のすべてを使用して、トレースプロセスがパッチアップが必要なものに遭遇するたびにシステムコールへの引数を変更することです。どちらも簡単に聞こえません...

または、chroot が機能するためには、重要なデバイス ノードとファイルの最小限のセットが chroot 内に存在する必要があることを受け入れることもできます。可能であれば、glibc の代わりに別の libc を使用すると、必要な追加ファイルの数を最小限に抑えることができます。

于 2011-09-30T15:49:31.973 に答える