Hatena::ブログ(Diary)

やねうらお−ノーゲーム・ノーライフ このページをアンテナに追加 RSSフィード

GT-Rの買取ならここですわ。どこよりも高く買取ってもらえるはず。お勧め!GT-R 買取
電王戦出場記念! 書籍化されたで! 監修したで!(`ω´) 絶版なってしもた Kindle版で復活!! 記事書いたで!
解析魔法少女美咲ちゃん マジカル・オープン!

YaneuLabs / やねうら王公式 / やねうらおにメール / twitter / プロフィール

 | 

2008-12-11 Native Clientの仕組みはどうなっているのか?

[] Native Clientの仕組みはどうなっているのか?  Native Clientの仕組みはどうなっているのか?を含むブックマーク  Native Clientの仕組みはどうなっているのか?のブックマークコメント


Googleが、Webブラウザ上でネイティブバイナリコードを実行する「Native Client」を公開した。


 Native Client

 http://code.google.com/p/nativeclient/


Google Earth,Quakeなどがすでに動いているようだ。


 これはすごい!ブラウザ上でX86バイナリを動かす「Native Client

 http://www.moongift.jp/2008/12/native_client/


 サンドボックスで安全性確保、オーバーヘッド5%

 ブラウザx86バイナリ実行、グーグルが新技術

 http://www.atmarkit.co.jp/news/200812/09/nacl.html


あと、サンプルのlife.ccにSDL_surfaceと書いてあるのでSDLの描画まわりが既に動いているのだろうから、今後、たくさんのアプリがNative Client移植されると思う。Native ClientActiveXの再来とかなんとか方々で散々叩かれているのだが、sandboxの仕組みがあるのがActiveXとは大きな違いだろう。


Native Client現在のところx86バイナリブラウザベースで実行できる。「C/C++で書いたコードに対してどうやればsandbox環境を提供できるのか?」について興味のある人も多いと思う。


そもそもそんなことが技術的に可能なら、docomoFOMAシリーズCPUリソースを食いまくるJavaなんて搭載しなかっただろうし、auだってBREWなんてうんこの漏る音のような採用しなかったかも知れないし、Android携帯にしてもJavaなんかではなくC/C++で書かせてくれたほうがよっぽど処理速度面においてiPhoneに対抗出来たはずだ。


さて、Native Clientはどのような仕組みでこのsandbox環境を実現しているのだろう?

本当にこのsandboxは安全なのか?ActiveXと本当に大きく違うのか?


論文をざっと追いかけていこう。


 Native Client: A Sandbox for Portable, Untrusted x86 Native Code

 http://nativeclient.googlecode.com/svn/trunk/nacl/googleclient/native_client/documentation/nacl_paper.pdf


■ Native Clientの概要


・信頼されていないx86コードを実行させるための、OSとbrowser-portableなsandboxを提供

セキュリティを損なうことなくWebアプリの実行速度をあげる

・threadをサポート

SSEなど拡張命令のサポート

インラインアセンブラで埋め込まれたコードサポート

・Native Clientで動作するように新しいコンパイラ言語サポートするのは難しくない

x86のセグメント機構を用いて単純化してオーバーヘッドの低減を達成


いまのところx86専用で、x86の動いているWindows,Linux,Mac上で動き、Native Client用のgccコンパイラコンパイルしたx86用のバイナリOS,ブラウザに対して可搬性があるので、それぞれの環境で動く。(この意味においてNative ClientOSっぽいと感じる)


他のCPUで動くブラウザターゲットとするなら、そのCPU用のNative Client用のgccコンパイラを持ってきて、コンパイルしたバイナリも用意しないといけないんだろうけど、そのへんはいずれサポートしているCPUすべてのバイナリを一発で作れるようになるだろうし、バイナリなのでファイルサイズは小さく、何種類もあってもWeb Serverにはそんなに負担はかけないだろう。


■ コード検証の仕組み


Native ClientJavaScriptとどう通信するのかだとかそのへんには全く興味がなくて、ただ単にx86コードをどう静的に解析するのかのみが私の興味の対象である。


この仕組みをここで書いておこう。


・到達可能なアドレスをすべて検証する。

コードの実行時の書き換えを禁止する。


大事なのはこの二つ。到達可能なコードの集合が得られれば、あとはそこで不正な命令が使われていないかをチェックすれば良い。


ここで言う不正な命令とは

・hlt

・syscall,int

・Ring0用の命令

・segment状態を変更するlds,far call

・ret(間接ジャンプが実現出来てしまうのでこれは使用しない)

である。


あと、x86のprefixをつけた記法も有用な命令と知られているものだけに制限する。


call , jmpで、アドレスは直接指定されているならその到達先は静的に解析できるので、事前に到達可能なコードをすべて列挙できる。ただし、call , jmpによる間接ジャンプは、ひと工夫必要だ。


このへんを含めて具体的に見ていこう。


まず、Native Cleint用のバイナリには次の7つの制約を課す。


制約1) いったんメモリにロードされたら、バイナリは書き換え不能とする。

これには、OSレベルメモリ保護機構を利用する。(WindowsならVirtualAllocですな)

制約2) バイナリは静的にリンクされ、そのスタートアドレスは0であり、その最初のtext領域(訳注:実行するコードを書けるところ)は64Kバイトである。

制約3)すべての間接ジャンプはnacljmpという疑似命令を用いる。

制約4) バイナリは、次のpage(訳注 : x86のpage frameのこと。x86ではdefaultでは1page = 4KB)まで1つ以上のhlt命令(0xf4)が詰められる。

制約5) バイナリは、32byte境界をまたぐx86命令や、Native Clientの疑似命令を含まないこと。

制約6) すべての有効な命令が配置されているアドレスは、バイナリベースアドレス(0)からfall throughによって到達可能であること。

制約7) すべての直接ジャンプジャンプ先が有効な命令群でなければならない。


だいたいの仕組みが見えてきた。コードの書き換えは、code page自体を書き換え不能にしておくことで実現している。


上の制約にはないが、もちろん、

・例外は、Native Clientのservice runtimeでcatch

する。


それからデータ保護という観点から、

・data sandbox外のデータを読み書きできてはならない

と言える。


これを実現するには、80386のsegmentを用いている。これにより仮想32bitアドレス空間連続した範囲(contiguous subrange)に対するデータアクセスを制限できる。sandboxのためのload/store命令が不要になるし、いい実装だ。(ただ、32bitというアドレス制限はある。Native Clientは64bitアドレス環境サポートしないのでいいのだろう。)


あと、呼び出しが許されるAPIは、Native Clientouter sandboxとして仮想化(wrap?)しているAPI群のみである…と思うのだが、そのへんは私は全く興味がないので詳しく見ていない。(ごめん)


最大の関心事である(?)制約3のnacljmp疑似命令がどういうコードに変換されるか見てみよう。


命令の途中(2バイト命令の2バイト目とか)に間接ジャンプされると困るので、制約3で書いたように間接ジャンプはnacljmp疑似命令を用いるが、これは、次の2つの命令(5バイト)に変換される。


and eax,0xffffffe0

jmp [eax]

すなわち、32バイト境界にしかジャンプ出来なくしてしまう。制約5により、32バイト境界をまたぐ命令は存在しないので、これにより、予期しない命令が実行される心配はない。


どうせ間接ジャンプが必要になるのは、switchcaseなどのテーブルジャンプであって、その場合は、ジャンプ先(caseベル)の最後にbreakしてswitchを抜けるのが普通だから、32バイトにalignするためにnopかhaltかをpaddingされているのは痛くはないということだろう。


あと、このsandbox validator(検証器)の実行速度は、

・approximately 30MB/second (35.7 MB in 1.2 seconds, measured on a MacBook Pro with MacOS 10.5, 2.4GHz Core 2 Duo CPU

とある。実行コードが30MBもあるバイナリなんてそうそうないから、どんなものでも1秒以内で終わると見ていいのだろう。


■ まとめ


Native Clientの仕組みはすこぶる単純だった。

見た通り、仮想化に取り組んでいる人には当たり前のテクニックだろう。


だけど、これをGoogleがやったという意義は大きいと思う。納品先からActiveX禁止令を通達されている人も「Native ClientならGoogleが作ってるし、100%安全ですよ」とかなんとかうまいことを言って、Native Clientで押し通せてしまうかも知れない。


しかし実際のところ、上で見たように、Native Clientバイナリを配置するときにやっている変換は、nacljmp疑似命令を置換するという単純なものなので、このバイナリの実行時のクロック数などを事前に計算できなくはないので、サイドチャネル攻撃に使える気は少しする。


あと外部APIの呼び出しにセキュリティホールがあると不正パラメータを渡して故意に不正コードを実行出来そうな気はしなくもない。まあ、Native Clientが広く使われればすぐにそんな穴は塞がれるんだろうけども。


まあ、それにしてもActiveXとは比較にならないほど安全と言えるだろう。


それからローカルなstorageにアクセスするAPIは…たぶん提供されないと思うので、「Googleデフラグなどデスクトップアプリブラウザベースで実現しようとしている」は、たぶん正しくない。どんなAPIが提供されるのか、もう少しNative Clientバージョンがあがったら改めて調べたい。


プログラムにこういう制約を与えて、あらゆるプログラムをRing0で動かせばRingを切り替えるオーバーヘッドが無くなるのになぁ…。って、それがMSのSingularityなのかな?よくは知らんけど。


■ 追記(2008/12/11 15:00)


スタックフレームを書き換えてretすると間接ジャンプが実現できてしまうじゃん」という突っ込みをいろいろいただいているんですが、風邪ぎみでぼーっとしてたんで書き忘れていました。(すみません)


これは、以下のような仕組みになっています。


・retも結局は間接ジャンプなので、間接ジャンプ命令扱いをする。


すなわち、戻り先は、32バイト境界でなくてはならないです。こうしておけば命令の途中のバイトにリターンしないことを保証できます。よって、call命令のあとは次の32バイト境界まではnopなどをpaddingするか、あるいは、call命令自体やめて、次の32バイト境界のアドレススタックに積んでjmpするコードになっているのではないかと思います。で、retの代わりにnacljmpがコンパイラで生成されているということかな?(未確認)


■ 参考リンク


 ブラウザX86マシン語を動かす! Google 謹製 Native Client をさっそく試してみる

 http://d.hatena.ne.jp/amachang/20081209/1228804423


 Native Client

 http://blog.deadbeaf.org/2008/12/09/google-native-client/


 Native Client必死こいて訳した1

 http://d.hatena.ne.jp/ranha/20081210

CACA 2008/12/11 08:05 retによる不正な間接ジャンプは静的検証だけでは阻止できなさそうな気がする感じ
(スタックフレームの書き換えにいくらでも複雑なアルゴリズムを導入できそう故、)
どのコンパイラで生成されたバイナリなのか認証の仕組みが要りそう、、

yaneuraoyaneurao 2008/12/11 14:59 > どのコンパイラで生成されたバイナリなのか認証の仕組みが要りそう、

その必要はありません。本文に追記しましたのでご覧を(´ω`)人

hogehoge 2008/12/11 21:18 32バイト境界に命令を置くのは、制約6, 7のチェック効率のためなんでしょうかね?
最初32ビット境界と読み間違えて and eax, 0xffffffe0 とかできないじゃんと思ったのは内緒・・・。

yaneuraoyaneurao 2008/12/11 21:29 text page(コード領域)の静的な検査の範囲については論文の最後のほうに書いてあります…が私は興味がなかったので読んでません。「Native Client必死こいて訳した」のranhaさんが訳してくれるのを待ちましょう(´ー`)b

 | 

1900 | 01 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 06 | 08 | 10 | 11 | 12 |
2015 | 01 | 02 |


Microsoft MVP
Microsoft MVP Visual C# 2006.07-2011.06