HN 日本語サマリー

← 一覧へ戻る
プログラミング

メモリ安全なインラインアセンブリ

Memory Safe Inline Assembly (fil-c.org)

83 pointsby pizlonator15 コメント

要約

Fil-Cコンパイラは、強力なインラインアセンブリ構文をメモリ安全にサポートする革新的な機能を導入しています。この機能は、コンパイラ分析の防止、CPU機能の特定、定数時間暗号化、アトミック操作など、C/C++コードでインラインアセンブリが依然として必要とされる多くのユースケースに対応します。Fil-Cは、アセンブリコードと制約を厳密に解析・検証することで、プログラマーの意図を維持しつつ、完全なメモリ安全性を保証します。

全文翻訳

メモリ安全なインラインアセンブリ 注記: これはプレリリース機能です。Fil-C 0.679リリースにはこの機能は含まれていません。この機能をテストするには、ソースからビルドする必要があります。 GCCとclangはどちらも信じられないほど強力なインラインアセンブリ構文をサポートしています。例えば: ``` unsigned rotate(unsigned x, unsigned char c) { asm("roll %1, %0" : "+r"(x) : "c"(c) : "cc"); return x; } ``` これは、コンパイラに`roll %1, %0`テンプレートに基づいてアセンブリを生成するよう指示します。ここで`%1`は`%cl`で埋められ、`%0`は`x`を保持するレジスタで埋められ、`c`は`roll`命令の直前に`%ecx`レジスタに移動されます。さらに、コンパイラには、この命令が`x`の値を変更し、制御フラグの値を変更することを伝えます。これは安全であるはずがないように見えます!もしプログラマが`"+r"`の`+`を省略したり、`"cc"`のクロバーを忘れたりしたらどうなるでしょうか?Yolo-Cでは、そのような間違いを犯すと、コンパイラは喜んでコードを誤コンパイルしてしまいます。しかし、Fil-Cはこのインラインアセンブリ構文をサポートしており、完全に安全です!このドキュメントでは、Fil-Cがなぜインラインアセンブリをサポートするのか、そしてプログラマの意図(要求したアセンブリテンプレートが得られること)と完全なメモリ安全性(何か間違ったことをしても、最悪の場合パニックになるか、不正な命令トラップが発生する)の両方を維持しながら、そのサポートがどのように実現されるかについて詳しく説明します。 なぜインラインアセンブリか? 人々のCおよびC++コードをレビューする中で、インラインアセンブリを使用する以下の理由を見つけました(1が最も一般的です)。 コンパイラ分析を防ぐための空のインラインアセンブリ。これには`asm volatile("" : : : "memory")`のようなものが含まれます。これは`atomic_signal_fence(memory_order_seq_cst)`を意味する古い方法です。これは、インラインアセンブリがすべてのメモリをクロバーするとコンパイラに伝えることで機能し、コンパイラにメモリアクセスを直列化させます。これはシグナルフェンスが行うことと同じです。コンパイラとの契約は明確です。コンパイラは、クロバーに関する主張を疑うことなく、要求されたアセンブリ(ここでは空)を正確に生成しなければなりません。つまり、アセンブリが空だからといってメモリクロバーがないと推測してはなりません。我々がメモリクロバーと言ったので、コンパイラはそれをそのように見なします。同様に、人々は`asm("" : "+r"(x))`のようなことを行います。これは、アセンブリが`x`を読み書きする可能性があることを意味します。アセンブリは空なので、アセンブリ実行後に`x`の値について何も知らないとコンパイラに仮定させる以外にコストはかかりません。この種のデータフローフェンスは、定数時間暗号を記述するのに役立ちます。Fil-Cは、それが自明に安全であるため、長い間空のインラインアセンブリをサポートしてきました。Fil-Cはポインタに対する`"+r"`制約もサポートしており、その場合、`intval`と`lower`の両方がLLVM IRレベルで独自の`"+r"`のような制約を通じて処理されます。 `cpuid`と`xgetbv`。これら2つの命令のインラインアセンブリスニペットは、SIMD組み込み関数を使用するコードで最も頻繁に現れます。これは、`cpuid.h`の`__get_cpuid` APIが使用が混乱を招き、私が知る限り、GCCとclangのどちらでも正しく機能しないためだと思います。そのため、zstd、simdutf、simdjsonなどのSIMDを使用するパッケージは、`cpuid`を呼び出すインラインアセンブリを使用してCPU機能を識別する傾向があります。彼らはしばしば`xgetbv`を呼び出すためにもインラインアセンブリを使用します。Fil-Cでは、`__get_cpuid`は修正されており、それを使用できます。また、`zxgetbv`は組み込み関数として提供されています。しかし、人々がコードを変更する必要なく、これらのインラインアセンブリスニペットをサポートする方が良いです!そして、コードが正しいクロバーと制約を指定している限り、`cpuid`と`xgetbv`を呼び出すことに関して危険なことは何もありません。 暗号コードにおける秘密の算術。素晴らしい例はOpenSSHのsntrup761実装で、キー算術をインラインアセンブリでラップして、入力に応じて実行時間が変動する可能性のある命令ではなく、正確な命令を確実に取得するようにしています。この種のコードは、インラインアセンブリがサポートされていない場合でも、コンパイラに定数時間コードを生成させるためのフォールバックをしばしば持っていますが、それらのフォールバックは厳密に検証される可能性が低く、パフォーマンスを低下させ、十分に巧妙なコンパイラによって回避される可能性のある「最適化ブロック」イディオムに依存していることがよくあります。したがって、これを行うインラインアセンブリスニペットをサポートするのが最も安全です。幸いなことに、これらのスニペットも、制約とクロバーが正しい限り、完全に安全です。 アトミック操作。コンパイラは長い間、アトミック命令の組み込み関数をサポートしてきました。コンパイラはまた、これらの組み込み関数を誤って実装してきた長い歴史を持っています!最近では、clangがARM64でCASをLL/SCに変換する方法にバグがありました。そのため、真剣なロックフリープログラマは、少なくとも一部の場合、インラインアセンブリを使用してアトミック命令を記述する傾向があります。例えば、誤コンパイルに遭遇し、アセンブリに落とすことがバグを修正する唯一の道であった場合などです。インラインアセンブリでのアトミック操作のサポートは、メモリにアクセスするインラインアセンブリを許可する必要があり、それはFil-Cの境界チェックをどのように推測するかを意味します。メモリにアクセスするインラインアセンブリは現在範囲外です。しかし、メモリ安全なインラインアセンブリはフェンス(lfence、sfence、mfence、serialize)をサポートしています。 システムコール。これらは現在、Fil-Cのインラインアセンブリの範囲外であり、それは問題ありません。システムコールのためにインラインアセンブリを使用する必要があるのは、libc実装の内部のみだからです。Fil-Cにはすでにmuslとglibcのポートがあり、どちらの場合もシステムコールのインラインアセンブリはFil-Cが提供するpizlonated_syscalls.h APIへの呼び出しに置き換えられています。しかし、将来、新しいlibcをFil-Cに簡単に移植できるように、システムコールを行うインラインアセンブリのサポートを追加することも考えられます。 x87 long double関数。x86でlong doubleを扱っている場合、x87の80ビット浮動小数点演算を使用しています。x87 FPUの様々な数学関数の実装にアクセスしたい場合、しばしばインラインアセンブリに落とすのが最善の方法です。これは、インラインアセンブリがx87スタックをプッシュまたはポップせず、制約がどのx87スタックレジスタがクロバーされたかを正しく記述している限り、完全に安全です。人々が他の目的でインラインアセンブリを使用している可能性はありますが、上記のリストはLinuxユーザーランドのプログラムを調査した際に見られたすべてです。まとめると:インラインアセンブリには依然として多くの正当な用途があります。インラインアセンブリの使用はCおよびC++ライブラリで広く普及しています。おそらくあなたもこの投稿を読んでいる間に複数のライブラリを使用しており、それらのライブラリのインラインアセンブリはクリティカルパス上にあります。インラインアセンブリの多くは自明に安全です。メモリにアクセスせず、制御フローを行わず、使用される命令には他の隠れた副作用がありません。世界初のメモリ安全なインラインアセンブリ実装の詳細については、引き続きお読みください! インラインアセンブリを安全にサポートする Fil-Cコンパイラの安全性計測パス(FilPizlonatorと呼ばれる)が実行されるとき、インラインアセンブリはLLVM IRに一対の文字列として存在します。 アセンブリ文字列。Cソースコードに表示されるものとほぼ同じですが、一部の文字が置き換えられています。例えば、`roll`の例は`roll $1, $0`になります。 制約文字列。これはLLVM固有の構文を使用して制約とクロバーを表現します。`roll`の例では、これは`=r,{cx},0,~{cc},~{dirflag},~{fpsr},~{flags}`です。 したがって、インラインアセンブリ式が安全であるかどうかを次のように検証できます。 アセンブリを解析・分析する。メモリアクセス、制御フロー、または認識できないものが含まれている場合、拒否します。 制約を解析・分析する。認識できない、またはサポートしないことを行っている場合、拒否します。 アセンブリの効果が制約によって完全に捕捉されていることを確認する。例えば、アセンブリ命令がレジスタを変更する場合、制約はそのレジスタの変更を捕捉しなければなりません。命令が一部のCPUフラグを設定する場合、それらのフラグはクロバーとしてリストされなければなりません。AIの登場前は、このようなコードを書くのは非常に困難でした。