web-dev-qa-db-ja.com

軽量のCコードサンドボックスを作成するにはどうすればよいですか?

ローカルおよびオンラインのソースから関数を収集できるCプリプロセッサ/コンパイラを構築したいと思います。すなわち:

#fetch MP3FileBuilder http://scripts.com/MP3Builder.gz
#fetch IpodDeviceReader http://Apple.com/modules/MP3Builder.gz

void mymodule_main() {
  MP3FileBuilder(&some_data);
}

それは簡単な部分です。

難しい部分はディスクまたはシステムリソース(メモリ割り当てとスタックを含む)への直接または無制限のアクセスからインポートされたコードを「サンドボックス化」する信頼できる方法が必要です。安全に実行する方法が欲しい信頼できないCコードの小さなスニペット(モジュール)それらを別々のプロセス、VMまたはインタープリター(別のスレッドただし、許容されます)。

[〜#〜]要件[〜#〜]

  • CPU時間を含むデータとリソースへのアクセスにクォータを設定する必要があります。
  • 標準ライブラリへの直接アクセスをブロックします
  • 無限の再帰を生み出す悪意のあるコードを止めたい
  • 静的および動的割り当てを特定の制限に制限したい
  • モジュールが発生する可能性のあるすべての例外(ゼロ除算など)をキャッチしたいと思います。
  • モジュールは、コアインターフェイスを介してのみ他のモジュールと対話できます
  • モジュールは、コアインターフェイスを介してのみシステム(I/Oなど)と対話できます。
  • モジュールは、ビット演算、数学、配列、列挙型、ループ、および分岐を許可する必要があります。
  • モジュールはASMを使用できません
  • モジュール用に予約されたメモリへのポインタと配列のアクセスを制限したい(カスタムsafe_malloc()を介して)
  • ANSI Cまたはサブセットをサポートする必要があります(以下を参照)
  • システムは軽量でクロスプラットフォーム(組み込みシステムを含む)である必要があります。
  • システムはGPLまたはLGPLと互換性がある必要があります。

Cのサブセットを受け入れることができてうれしいです。テンプレートやクラスのようなものは必要ありません。私は主に、高速数学、ビット演算、バイナリデータの検索と処理など、高級言語ではうまくいかないことに興味があります。

not既存のCコードを変更せずに再利用してモジュールを作成できるようにすることを目的としています。モジュールは、モジュールを基本的なロジックおよび変換操作(ビデオトランスコードや圧縮操作など)に制限するように設計された一連のルールと制限に準拠する必要があります。

このようなコンパイラ/プリプロセッサへの理論的な入力は、module_main関数を含む単一のANSI Cファイル(または安全なサブセット)であり、インクルードまたはプリプロセッサディレクティブはなく、ASMはありません。ループ、分岐、関数呼び出し、ポインタが許可されます。数学(モジュールに割り当てられた範囲に制限されます)、ビットシフト、ビットフィールド、キャスト、列挙型、配列、int、float、文字列、および数学。それ以外はオプションです。

例の実装

これをよりよく説明するための疑似コードスニペットを次に示します。ここで、モジュールはそのメモリ割り当てクォータを超え、無限再帰も作成します。

buffer* transcodeToAVI_main( &in_buffer ) {
    int buffer[1000000000]; // allocation exceeding quota
    while(true) {} // infinite loop
    return buffer;
}

これは、プリプロセッサがメモリ使用量と再帰をチェックするためのウォッチポイントを追加し、すべてを例外ハンドラでラップした変換バージョンです。

buffer* transcodeToAVI_main( &in_buffer ) {
    try {
        core_funcStart(__FILE__,__FUNC__); // tell core we're executing this function
        buffer = core_newArray(1000000000, __FILE__, __FUNC__); // memory allocation from quota
        while(true) {
           core_checkLoop(__FILE__, __FUNC__, __LINE__) && break; // break loop on recursion limit
        } 
        core_moduleEnd(__FILE__,__FUNC__);
    } catch {
        core_exceptionHandler(__FILE__, __FUNC__);
    }
    return buffer;
}

これらのチェックを実行するとモジュールのパフォーマンスに影響することはわかっていますが、解決しようとしているタスクの高レベルまたはVM言語)よりもパフォーマンスが優れていると思います。モジュールの危険な実行を停止しようとはしていません物事を完全に、私はそれらの危険なことを制御された方法で(ユーザーフィードバックなどを介して)強制的に発生させようとしています。つまり、「モジュールXがメモリ割り当てを超えました、続行しますか、それとも中止しますか?」.

[〜#〜]更新[〜#〜]

私がこれまでに得た最善の方法は、境界チェックといくつかのカスタム関数とループコードを備えたカスタムコンパイラ(ハッキングされたTCCのような)を使用して再帰をキャッチすることです。他に何をチェックする必要があるのか​​、どのような解決策があるのか​​についての考えを聞きたいです。 ASMを削除し、使用前にポインターをチェックすることで、以下の以前の回答で示された多くの懸念が解決されると思います。 SOコミュニティからさらにフィードバックを引き出すために、賞金を追加しました。

私が探している恵みのために:

  • 上で定義された理論システムに対する潜在的なエクスプロイトの詳細
  • 各アクセスでポインタをチェックする上で可能な最適化
  • 概念の実験的なオープンソース実装(Google Native Clientなど)
  • 幅広いOSとデバイスをサポートするソリューション(OS /ハードウェアベースのソリューションなし)
  • ほとんどのC操作、またはC++(可能な場合)をサポートするソリューション

GCCで動作できるメソッド(つまり、プリプロセッサまたはsmallGCCパッチ)の追加クレジット。

また、私がやろうとしていることがまったくできないことを決定的に証明できる人にも配慮します。しかし、これまでのところ、それが不可能であると彼らが考える理由の技術的側面を実際に釘付けにしている異議はないので、かなり説得力がある必要があります。この質問は元々C++を安全に実行する方法として提起されたものではないと言った人々を擁護するために。これで、要件をCの限定されたサブセットに縮小しました。

私のCの理解は「中級」に分類できますが、PCハードウェアの私の理解はおそらく「上級」よりも一歩下です。可能であれば、そのレベルの答えを指導してみてください。私はCの専門家ではないので、主に回答に与えられた投票と、回答が私の要件にどれだけ近いかに基づいて説明します。あなたはあなたの主張(回答者)に十分な証拠を提供し、投票(他のすべての人)によって支援することができます。バウンティカウントダウンが6時間に達したら、回答を割り当てます。

最後に、この問題を解決することは、ますますネットワーク化され、妄想的な世界でCの関連性を維持するための大きな一歩になると思います。他の言語がパフォーマンスの面でギャップを埋め、計算能力が高まるにつれて、C開発の追加のリスクを正当化することはますます難しくなります(現在はASMの場合のように)。あなたの回答は、いくつかのSOポイントを獲得するよりもはるかに関連性が高いと思いますので、賞金の期限が切れていても、できる限り貢献してください。

37
SpliFF

C標準は広すぎて許可できないため、逆の方法をとる必要があります。必要なCの最小サブセットを指定し、それを実装してみてください。 ANSI Cでさえ、すでに複雑すぎて、望ましくない動作を許容します。

最も問題となるCの側面はポインターです。C言語はポインター算術を必要とし、それらはチェックされません。例えば:

_char a[100];
printf("%p %p\n", a[10], 10[a]);
_

両方とも同じアドレスを出力します。 a[10] == 10[a] == *(10 + a) == *(a + 10)以降。

これらすべてのポインタアクセスは、コンパイル時にチェックすることはできません。これは、停止問題の解決を必要とする「プログラム内のすべてのバグ」をコンパイラーに要求するのと同じ複雑さです。

この関数を同じプロセスで(場合によっては別のスレッドで)実行できるようにしたいので、アプリケーションと「安全な」モジュールの間でメモリを共有します。これがスレッドを持つことの全体的なポイントであるためです。データを共有してアクセスを高速化します。ただし、これは、両方のスレッドが同じメモリを読み書きできることも意味します。

また、ポインタが終わるコンパイル時間を証明することはできないため、実行時にそれを行う必要があります。つまり、「a [10]」のようなコードは「get_byte(a + 10)」のようなものに変換する必要があり、その時点でCとは呼ばなくなります。

Google Native Client

それで、それが本当なら、グーグルはそれをどのように行うのでしょうか?ここでの要件(クロスプラットフォーム(組み込みシステムを含む))とは対照的に、Googleはx86に重点を置いています。これは、ページ保護によるページングに加えて、レジスタもセグメント化します。これにより、別のスレッドが同じメモリを同じ方法で共有しないサンドボックスを作成できます。サンドボックスは、セグメンテーションによって、独自のメモリ範囲のみを変更するように制限されます。さらに:

  • 安全なx86アセンブリ構造のリストが組み立てられます
  • gccは、これらの安全な構造を放出するように変更されています
  • このリストは、検証可能な方法で作成されています。
  • モジュールをロードした後、この検証が行われます

したがって、これはプラットフォーム固有であり、「単純な」ソリューションではありませんが、機能するソリューションです。詳細については、 研究論文 をご覧ください。

結論

したがって、どのルートを使用する場合でも、検証可能な新しいものから始める必要があります。そうして初めて、既存のコンパイラを適応させるか、新しいコンパイラを生成することができます。ただし、ANSI Cを模倣しようとすると、ポインタの問題について考える必要があります。 Googleは、サンドボックスをANSI Cではなくx86のサブセットでモデル化したため、x86に関連付けられているという欠点があり、既存のコンパイラを大幅に使用できました。

15

ブラウザでx86コードを(安全に、私たちは願っています)実行するためのシステムである Native Client を設計するときにGoogleが行った実装上の懸念と選択のいくつかについて読むことから多くを得ると思います。コードを安全でない場合は、makeにするために、ソースの書き換えまたはソースからソースへのコンパイルを行う必要がある場合がありますが、信頼できるはずです。 NaCLサンドボックスで、ファンキーすぎることを実行しようとした場合に、生成されたアセンブリコードをキャッチします。

9
Matt J

これは簡単なことではありませんが、それほど難しくはありません。

サンドボックスでバイナリコードを実行できます。すべてのオペレーティングシステムはこれを一日中行います。

彼らはあなたの標準ライブラリを使わなければならないでしょう(一般的なCライブラリに対して)。標準ライブラリは、課したいコントロールを強制します。

次に、実行時に「実行可能なコード」を作成できないようにする必要があります。つまり、スタックは実行可能ではなく、実行可能なメモリを割り当てることもできません。つまり、コンパイラ(YOURコンパイラ)によって生成されたコードのみが実行可能になります。

コンパイラが実行可能ファイルに暗号で署名する場合、ランタイムは改ざんされたバイナリを検出でき、単にそれらをロードしません。これにより、単に必要のないものをバイナリに「突っ込む」ことができなくなります。

「安全な」コードを生成する制御されたコンパイラと、制御されたシステムライブラリを使用すると、実際の機械語コードを使用しても、合理的に制御されたサンドボックスが得られます。

メモリ制限を課したいですか? mallocにチェックインします。割り当てられるスタックの量を制限したいですか?スタックセグメントを制限します。

オペレーティングシステムは、仮想メモリマネージャを使用して、このような制約のある環境を1日中作成するため、最新のOSでこれらのことを簡単に実行できます。

これを行う努力が、既成の仮想マシンとバイトコードランタイムを使用するのと比較して価値があるかどうかは、私には言えません。

5
Will Hartung

これを行う場合は、次の2つのアプローチのいずれかを調査します。

  • CERNの [〜#〜] cint [〜#〜] を使用して、インタープリターでサンドボックス化されたコードを実行し、インタープリターが許可するものの制限について確認します。これはおそらくひどく良いパフォーマンスを与えないでしょう。
  • [〜#〜] llvm [〜#〜] を使用してC++コードの中間表現を作成し、サンドボックス化されたJavaスタイルのVMでそのバイトコードを実行できるかどうかを確認します。

しかし、私はこれが恐らく恐ろしく複雑なプロジェクトであることに同意します。ブラウザ全体を不安定にするバグのあるプラグインやハングしたプラグインでWebブラウザが抱えていた問題を見てください。または、 Wireshark プロジェクトのリリースノートをご覧ください。ほとんどすべてのリリースには、プログラム全体に影響を与えるプロトコルディセクタの1つに問題がある場合のセキュリティ修正が含まれているようです。 C/C++サンドボックスが実現可能であれば、これらのプロジェクトは今では1つに固定されていると思います。

5
Josh Kelley

Tiny Cコンパイラ(TCC) に出くわしました。これは私が必要なものかもしれません:

*  SMALL! You can compile and execute C code everywhere, for example on rescue disks (about 100KB for x86 TCC executable, including C preprocessor, C compiler, assembler and linker).
* FAST! tcc generates x86 code. No byte code overhead. Compile, assemble and link several times faster than GCC.
* UNLIMITED! Any C dynamic library can be used directly. TCC is heading torward full ISOC99 compliance. TCC can of course compile itself.
* SAFE! tcc includes an optional memory and bound checker. Bound checked code can be mixed freely with standard code.
* Compile and execute C source directly. No linking or Assembly necessary. Full C preprocessor and GNU-like assembler included.
* C script supported : just add '#!/usr/local/bin/tcc -run' at the first line of your C source, and execute it directly from the command line.
* With libtcc, you can use TCC as a backend for dynamic code generation.

これは非常に小さなプログラムであり、ハッキングを実行可能なオプションにします(GCCをハックしますか?、この生涯ではありません!)。独自の制限付きコンパイラを構築するための優れた基盤になると思います。安全にできない言語機能のサポートを削除し、メモリ割り当てとループ処理をラップまたは置換します。

TCCはすでに メモリアクセスの境界チェック を実行できます。これは私の要件の1つです。

libtccも優れた機能です。これは、コードのコンパイルを内部で管理できるためです。

簡単だとは思いませんが、リスクを抑えてCに近いパフォーマンスが得られることを期待しています。

それでも他のアイデアを聞きたいです。

4
SpliFF

完全に不可能です。言語はこのようには機能しません。クラスの概念は、GCCを含むほとんどのコンパイラで非常に早い段階で失われます。たとえそうであったとしても、「モジュール」は言うまでもなく、各メモリ割り当てをライブオブジェクトに関連付ける方法はありません。

3
MSalters

私はこれをあまり詳しく調べていませんが、Chromium(別名Google Chrome)に取り組んでいる人たちは、すでにほぼこのようなサンドボックスに取り組んでいます。これは調べる価値があるかもしれません。

http://dev.chromium.org/developers/design-documents/sandbox/Sandbox-FAQ

オープンソースなので、使えるはずです。

3
Epcylon

言語がチューリング完全である場合、すべての可能なコードについて、コードのセットが安全であるか安全でないかを判断できる静的コードベリファイアを作成することは不可能です。これは停止性問題に相当します。

もちろん、スーパーバイザーコードがより低いリングレベルで実行されている場合、またはインタープリター言語である場合(つまり、マシンリソースをエミュレートしている場合)、この点は重要ではありません。

これを行う最良の方法は、別のプロセスでコードを開始し(ipcはそれほど悪くはありません)、LinuxでPtraceのようなシステムコールをトラップすることです http://linux.die.net/man/2/ptrace

2
Unknown

Liranは、上記のコメントで codepad.org を指摘しました。非常に重い環境(ptrace、chroot、およびアウトバウンドファイアウォールで構成される)に依存しているため、適切ではありませんが、ここで共有すると思ったg ++安全スイッチがいくつか見つかりました。

gcc 4.1.2フラグ:-O -fmessage-length = 0 -fno-merge-constants -fstrict-aliasing -fstack-protector-all

g ++ 4.1.2フラグ:-O -std = c ++ 98 -pedantic-errors -Wfatal-errors -Werror -Wall -Wextra -Wno-missing-field-initializers -Wwrite-strings -Wno-deprecated -Wno-unused- Wno-non-virtual-dtor -Wno-variadic-macros -fmessage-length = 0 -ftemplate-depth-128 -fno-merge-constants -fno-nonansi-builtins -fno-gnu-keywords -fno-elide-constructors- fstrict-aliasing -fstack-protector-all -Winvalid-pch

オプションは GCCマニュアル で説明されています

本当に目を引いたのは、スタックプロテクターフラグでした。これは、このIBMの研究プロジェクト( Stack-Smashing Protector )と公式のGCCのマージだと思います。

保護は、バッファオーバーフローの検出と、ポインタの破損を回避するための変数の並べ替え機能によって実現されます。バッファオーバーフロー検出の基本的な考え方は、 StackGuard システムに由来します。

新しい機能は、(1)ポインタの後にバッファを配置して任意のメモリ位置をさらに破損する可能性のあるポインタの破損を回避するためのローカル変数の並べ替え、(2)ローカル変数の前の領域への関数引数のポインタのコピーです。任意のメモリ位置をさらに破損するために使用される可能性のあるポインタの破損を防ぐためのバッファ、および(3)パフォーマンスのオーバーヘッドを減らすための一部の関数からのインストルメンテーションコードの省略。

2
SpliFF

8年後、私は元の要件をすべて満たす新しいプラットフォームを発見しました。 Web Assembly ブラウザ内でC/C++サブセットを安全に実行でき、メモリアクセスの制限や、OSおよび親プロセスでの安全でない操作の防止など、要件と同様の安全制限があります。これはFirefox52に実装されており、他のブラウザが将来サポートするという有望な兆候があります。

1
SpliFF

本当に確信したいのであれば、これを行うための最善かつおそらく唯一の方法は、別々のプロセスのラインをたどり、O/Sにアクセス制御を処理させることだと思います。 that汎用のスレッドローダーを作成するのは面倒ではありません。一度作成したら、いくつかの関数をオーバーライドして特定のライブラリをロードできます。

0
Jon Cage

あなたは2つの非問題を解決しようとしているようです。私自身のコードでは、メモリ割り当ての問題や、再帰や無限ループの問題はありません。

あなたが提案しているように見えるのは、C++とは異なり、より制限された言語です。これはもちろん追求できることですが、他の人が指摘しているように、コンパイラを作成する必要があります。単純なテキスト処理では、必要なものが得られません。

0
anon

いい考えですが、あなたがやろうとしていることはCやC++では不可能だと私は確信しています。サンドボックスのアイデアを落とした場合、それはうまくいくかもしれません。

JavaはすでにMaven2に同様の(サードパーティコードの大規模なライブラリのように)システムを持っています

0
Glen