39

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

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

モジュールを作成するために、既存の 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 で動作するメソッド (つまり、プリプロセッサまたは小さなGCC パッチ) に対する追加の功績。

また、私が試みていることがまったく不可能であることを決定的に証明できる人を検討します。ただし、これまでのところ、反対意見のどれもが、不可能だと考える理由の技術的側面を本当に釘付けにしていないため、かなり説得力がある必要があります。いいえと答えた人を弁護するために、この質問はもともと C++ を安全に実行する方法として提起されました。要件を C の限られたサブセットに縮小しました。

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

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

4

13 に答える 13

15

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 ネイティブ クライアント

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

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

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

結論

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

于 2009-06-14T18:26:55.940 に答える
10

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

于 2009-06-12T06:25:42.197 に答える
5

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

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

彼らはあなたの標準ライブラリを使用する必要があります (対汎用 C ライブラリ)。標準ライブラリは、課したい制御を強制します。

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

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

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

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

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

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

于 2009-06-12T04:14:10.687 に答える
5

これを行う場合、次の 2 つの方法のいずれかを調査します。

  • CERN のCINTを使用して、サンドボックス化されたコードをインタープリターで実行し、インタープリターが許可するものを制限する方法を確認します。これはおそらく、非常に優れたパフォーマンスを提供するものではありません。
  • LLVMを使用して C++ コードの中間表現を作成し、サンドボックス化された Java スタイルの VM でそのバイトコードを実行できるかどうかを確認します。

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

于 2009-06-11T12:59:22.290 に答える
5

Tiny C Compiler (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 に近いパフォーマンスを実現できることを願っています。

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

于 2009-06-11T14:10:27.873 に答える
3

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

于 2009-06-11T09:44:58.260 に答える
3

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

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

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

于 2009-06-14T18:57:46.660 に答える
2

言語がチューリング完全である場合、考えられるすべてのコードについて、一連のコードが安全か安全でないかを判断できる静的コード検証器を作成することは不可能です。停止問題と同じです。

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

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

于 2009-06-12T06:34:27.527 に答える
2

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 マニュアルで説明されています

特に目を引いたのは、stack-protector フラグです。これは、この IBM の研究プロジェクト ( Stack-Smashing Protector ) と公式の GCC を統合したものだと思います。

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

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

于 2009-06-17T01:56:37.507 に答える
0

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

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

于 2009-06-11T12:23:02.750 に答える
0

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

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

于 2009-06-11T09:36:48.247 に答える
0

本当に確実にしたい場合は、これを行うための最善かつおそらく唯一の方法は、個別のプロセスの行をたどって、O / Sにアクセス制御を処理させることだと思います. 一般的なスレッド化されたローダーを作成するのはそれほど難しくありません。それができたら、いくつかの関数をオーバーライドして特定のライブラリをロードできます。

于 2009-06-11T09:41:45.537 に答える