2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
バイナリを書き換えてシステムコールをフックする(全1記事)
リンクをコピー
記事をブックマーク
yasukata氏(以下、yasukata):yasukataといいます。発表を始めます。
今回は、「Zpoline」という、バイナリを書き換えることでシステムコールをフックする仕組みを紹介します。ここではx84-64のCPUで動作するLinuxを想定しています。(スライドを示して)ソースコードはこちらにURLがあるので、よろしければ見てみてください。あとでスライドも公開するので、そちらも併せてご覧ください。
まず、なぜシステムコールをフックしたくなったのかですが、個人的にカーネルに実装されている機能を、アプリケーションを変えないでユーザー空間で置き換えたいと思ったからです。
ネットワークスタックをユーザー空間に持っていきたいと思った時、システムコールをフックして、適宜ユーザー空間実装を実行すればよさそうだと考えて、作り始めました。その時に「もしかすると、この用途に合ったシステムコールをフックする仕組みはないのでは?」と気づきました。
システムコールをフックするには具体的に4つの要件があります。1つ目はフックの適用後にアプリケーションの性能劣化が小さいこと。2つ目は、フック適用の確度が高い、つまりフックし損ねないこと。3つ目は、ユーザー空間プログラムの再コンパイルが不要であること。4つ目は、カーネルを変える必要がなく、カーネルモジュールもいらないことです。
調べてみた結果、代表的なところを3つ上げると、ptraceのような既存のカーネル機能や、LD_PRELOADを使ったライブラリ関数の置き換え、既存のバイナリ書き換え手法などがありました。これらを一見したところ、既存の仕組みでは、「性能」と「フックをし損ねないこと」の両立が難しそうでした。
今回のモチベーションは、それらを両立する仕組みを作りたいということです。ここで紹介するZpolineという仕組みは、バイナリ書き換えでシステムコールをフックします。バイナリ書き換えの特性上、性能の劣化は抑えやすいのですが、一方で、既存の仕組みでも起こるように、フックをし損ねてしまうことがあります。
なので、どうすればバイナリ書き換えでフックし損ねないようにできるかが今回のチャレンジで、なぜそれが起こるのかと、何が難しいのかをお話しします。
x86-64のCPUでシステムコールを発行しようとすると、基本的にsyscallもしくはsysenterというCPU命令が使われます。これらはそれぞれ2byteですが、具体的にやりたいのは、syscall、sysenter命令を置き換えて、任意のフック関数のアドレスへジャンプすることです。
この時難しいのは、任意のアドレスを指定するのに2byteでは小さくて、2byteを超えてしまうと、ほかの命令を壊してしまうということです。
このため、既存のバイナリ書き換えの仕組みでは、確実な置き換えの保証が難しいという問題があります。
今回のZpolineの考え方ですが、2byteでジャンプ先のアドレスを指定するのは難しそうなので、代わりにシステムコールの呼出規約を利用した書き換えを行って、かつ、適切にトランポリンコードを用意する方向性でやっていきます。
では、どのようにバイナリを書き換えるのかですが、Zpolineでは、syscall、sysenter命令を「callq *%rax」へ置き換えます。読みにくいので「コールアールエーエックス」と呼びます。
ポイントは、callq *%raxはオペコードが「0xff 0xd0」の2byteなので、syscall、sysenterをそのまま置き換えられることです。「置き換えた後はどうなるの?」と思われるかもしれませんが、callq *%raxを実行すると、raxレジスタに入っている値を宛先アドレスと解釈してジャンプします。
さらに「それどうなるの?」と思われることについては、ここでシステムコールの呼出規約が利いてきます。x86-64のCPUで動いているLinux上では、ユーザー空間プログラムは利用したいシステムコールの番号をraxレジスタに入れた後に、syscall、sysenter命令を実行することが決められています。
システムコール番号は、カーネルが中でシステムコールを識別するために定義している番号で、例えばreadシステムコールだったら0、writeシステムコールだったら1、と決まっています。システムコールは合計4〜500個あるので、システムコール番号は400から500ぐらいまでとなっています。
syscall、sysenter命令が実行される時には、raxレジスタにシステムコール番号が入っています。syscall、sysenterをcallq *%raxで置き換えると、アドレス0から400、500程度までジャンプするところがポイントです。
なのでここでは、callq *%raxでジャンプしてくるアドレス0から500程度までを含む領域に、トランポリンコードを用意していきます。(スライドを示して)Linuxでは、こちらのコマンドにあるように、procfsから設定するとmmapでアドレス0にメモリを確保できるようになります。
ちなみに、Zpolineの名前は、アドレス0に置かれるトランポリンコードというところから来ています。
(スライドを示して)具体的にトランポリンコードの中身はこのようになっています。まず、システムコール番号の数だけ先頭をnopで埋めます。その直後に任意のフックへのジャンプのコードを置きます。
これによって、callq *%raxを実行すると、最初に置いたnopのどれかに着地して、その後は、下に続いているnopを辿ってフックへジャンプする処理まで行きます。そして、任意のフックに飛んでくれます。これで任意のフックへジャンプする処理が書けました。
今回は、初期化する部分をLD_PRELOADで最初にロードされることを想定した共有ライブラリとして実装して、この例のように実行します。すると、トランポリンコードの用意とバイナリの書き換えを、a.out内のmain関数が開始する前に実行します。
バイナリ書き換え自体は、メモリにロードされたプログラムに対して行うので、プログラムファイル自体の変更は必要ありません。
フックのオーバーヘッドがどのようなものかを知るために、実際にシステムコールのフックを適用した後に、getpidという軽いシステムコールを1回実行するために必要なCPUサイクルを計測しました。
2パターン試しました。フック関数の中でgetpidシステムコールを実際に発行して結果を返すパターンと、メモリ上にキャッシュしたpidの値を返すパターンです。テーブルには、pidキャッシュなし、あり、という区別で書いてあります。
今回は、ユーザー空間でシステムコールを置き換えて自分で実装したかったので、pidキャッシュありのほうが、今回のケースでのオーバーヘッドがより見えるようになっていると思っています。
この環境で計った感じでは、Zpolineでシステムコールをフックした場合、ptraceで実装する場合に比べて、getpidが100倍以上高速にエミュレートできる結果になりました。
まとめです。今回はZpolineという、バイナリを書き換えることでシステムコールをフックする仕組みを紹介しました。ソースコードは(スライドを示して)こちらのURLにあるので、よろしければ試してみてください。
それから、今回は時間の関係で省いたのですが、具体的なフック関数のプログラミングの方法については、新しく記事を用意したので、よろしければこちらもご覧ください。以上です。ご清聴ありがとうございました。
司会者:ありがとうございます。けっこう質問が出ますね。「純粋なフックなしに比べれば、逆にどれくらいのオーバーヘッドなんだろう?」「ほかより速いのはわかったけど、オーバーヘッドはどれくらい?」という質問があります。
yasukata:(スライドを示して)pidキャッシュありのこの数字は、メモリ上にキャッシュしたpidの値を返しているだけなので、これはかなり純粋なオーバーヘッドに近いと思っています。
関連タグ:
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05