5

呼び出しプロセスからページをマップするためにget_user_pages(または)を使用するLinuxドライバーについて考えてみます。get_page次に、ページの物理アドレスがハードウェアデバイスに渡されます。当事者が通信を終了することを決定するまで、プロセスとデバイスの両方がページの読み取りと書き込みを行うことができます。特に、呼び出し元のシステムコールがget_user_pages戻った後も、通信はページを使用し続ける場合があります。システムコールは、事実上、プロセスとハードウェアデバイスの間に共有メモリゾーンを設定しています。

プロセスが呼び出した場合に何が起こるかについて心配していますfork(別のスレッドからのものである可能性があり、呼び出しを行うシステムコールget_user_pagesの進行中またはそれ以降に発生する可能性があります)。特に、親がフォークの後に共有メモリ領域に書き込む場合、基になる物理アドレス(おそらくコピーオンライトのために変更された)について何を知っていますか?理解したい:

  1. 潜在的に誤動作するプロセスから防御するためにカーネルが行う必要があること(セキュリティホールを作成したくない!)。
  2. ドライバの機能が正しく機能するためにプロセスが従う必要のある制限(つまり、物理メモリが親プロセスの同じアドレスにマップされたままになる)。

    • 理想的には、子プロセスがドライバーをまったく使用しない(おそらくexecほとんどすぐに呼び出される)一般的なケースを機能させたいと思います。
    • 理想的には、スタック割り当てバッファをドライバに渡す既存のコードがあるため、メモリを割り当てるときに親プロセスが特別な手順を実行する必要はありません。
    • 私はを知っmadviseMADV_DONTFORKいます。子プロセスのスペースからメモリを消してもかまいませんが、スタックに割り当てられたバッファには適用できません。
    • 「ドライバーとの接続がアクティブな間はフォークを使用しないでください」というのは煩わしいことですが、ポイント1が満たされれば、最後の手段として受け入れられます。

ドキュメントやソースコードを紹介したいと思います。特にLinuxデバイスドライバーを調べましたが、この問題が解決されていませんでした。カーネルソースの関連部分だけに適用されるRTFSは、少し圧倒されます。

カーネルバージョンは完全には修正されていませんが、最近のものです(たとえば≥2.6.26)。重要な場合は、Armプラットフォーム(これまではシングルプロセッサですが、マルチコアはもうすぐです)のみを対象としています。

4

2 に答える 2

4

Afork()は干渉しませんget_user_pages()get_user_pages()あなたに。を与えますstruct page

アクセスする前にアクセスする必要がありkmap()ます。このマッピングは、ユーザースペースではなくカーネルスペースで行われます。

編集:get_user_pages()ページテーブルをタッチしますが、これについて心配する必要はありません(ページがユーザースペースにマップされていることを確認するだけです)。問題が発生した場合は-EFAULTを返します。

fork()を実行すると、コピーオンライトが実行されるまで、子はそのページを見ることができます。コピーオンライトが実行されると(子/ドライバー/親がユーザースペースマッピングを介してページに書き込んだため、ドライバーが持っているカーネルkmap()ではないため)、そのページは共有されなくなります。ページ上で(ドライバーコード内で)kmap()を保持している場合、親ページを保持しているのか子ページを保持しているのかを知ることはできません。

1)execve()を実行すると、すべてが失われるため、これはセキュリティホールではありません。

2)fork()を実行するときは、両方のプロセスを同一にする必要があります(これはフォークです!!)。あなたのデザインは、親と子の両方がドライバーにアクセスできるようにするべきだと思います。Execve()はすべてをフラッシュします。

次のような機能をユーザースペースに追加するのはどうですか。

 f = open("/dev/your_thing")
 mapping = mmap(f, ...)

デバイスでmmap()が呼び出されると、特別なフラグを使用してメモリマッピングをインストールします:http: //os1a.cs.columbia.edu/lxr/source/include/linux/mm.h#071

次のような興味深いものがあります。

#define VM_SHARED       0x00000008
#define VM_LOCKED       0x00002000
#define VM_DONTCOPY     0x00020000      /* Do not copy this vma on fork */

VM_SHAREDは書き込み時にコピーを無効にしますVM_LOCKEDはそのページのスワッピングを無効にしますVM_DONTCOPYはカーネルにフォークのvma領域をコピーしないように指示しますが、それは良い考えではないと思います

于 2010-10-28T19:20:33.187 に答える
3

簡単な答えはmadvise(addr, len, MADV_DONTFORK)、ドライバーに提供するすべてのユーザースペースバッファーで使用することです。これは、マッピングを親から子にコピーしてはならないため、CoWがないことをカーネルに通知します。

欠点は、子がそのアドレスでマッピングを継承しないことです。そのため、子にドライバの使用を開始させたい場合は、そのメモリを再マッピングする必要があります。しかし、それはユーザースペースで行うのはかなり簡単です。

更新:スタック上のバ​​ッファーには問題があります。一般的に安全にできるかどうかはわかりません。

DONTFORK子がフォークしたときにそのスタックページで実行されている可能性があるため、または(ある意味でさらに悪いことに)後で関数が戻ってマップされていないスタックページにヒットする可能性があるため、マークを付けることはできません。(私もこれをテストしました。スタックにDONTFORKのマークを付けることができます。フォークすると、悪いことが起こります)。

CoWを回避するもう1つの方法は、共有マッピングを作成することですが、明らかな理由でスタックを共有にマッピングすることはできません。

つまり、フォークするとCoWのリスクがあります。子が「ただ」実行したとしても、それでもスタックページに触れて、CoWを引き起こし、親が別のページを取得することにつながる可能性があります。これは悪いことです。

有利な点の1つは、スタック上のバ​​ッファーを使用するコードは、フォークを呼び出すコードについてのみ心配する必要があるということです。関数が戻った後は、スタック上のバ​​ッファーを使用できません。したがって、呼び出し先を監査するだけで済みます。呼び出し先がフォークしない場合は安全ですが、それでも実行不可能な場合があり、コードが変更された場合は脆弱です。

ドライバーに与えられるすべてのメモリーを、ユーザースペースのカスタムアロケーターから取得する必要があると思います。それはそれほど邪魔になるべきではありません。アロケータはmmap、他の回答が示唆しているように、デバイスを直接使用することも、匿名mmapを使用することもできます。madvise(DONTFORK)おそらくmlock()、スワップアウトを回避するためです。

于 2010-10-28T22:46:42.557 に答える