関数の型情報をハッシュにした物をターゲットの命令列を生成するバックエンドまで持ってこないといけない為、変更はフロントエンド、RTL、バックエンドの3層全てにおよび、テスト込みで6600行程度の変更になったらしい。このkcfiの実装はx86_64、aarch64、arm、risc-vをサポートしている

CFIにはいくつかの実現方法があるが、Linuxカーネルのような低レイヤーで使いやすいように作られたのがclangのkcfi。kcfiは関数の先頭の命令の前に関数の型のハッシュ値を埋め込む。間接関数呼び出し時は呼び出す関数の型のハッシュ値と、ジャンプ先の前に書かれたハッシュ値が一致するかを確認する。
これは意図しない関数の呼び出しを完全に防ぐ物ではないが、関数の型は多様なので呼べる関数の種類はごく僅かになる。Linuxカーネルは以前からclangでビルドする場合に限りkcfiを仕込んでビルドする事ができた。何故clang限定だったかというとgccにはkcfiの実装が無かったから。
そこでKees Cook氏はclangのkcfiと同じように機能するkcfiをgccにも実装して、gccでビルドするLinuxでもcfiを使えるようにした。clangでビルドしたバイナリとgccでビルドしたバイナリが互いの関数を呼べるようにgccのkcfiにはgccに実装があり、clangでもkcfiのハッシュに指定可能なFNV-1aが選ばれた

これでリターンアドレスの書き換えで任意のコードを実行する事はできなくなるが、スタックやヒープ上に関数ポインタが置かれていて、それが特定の条件で呼び出される場合(間接関数呼び出し)、攻撃者は関数ポインタのアドレスを書き換える事でROPと同じようにCall-oriented programming(COP)ができる。
そこでIntel Control-Flow Enforcement Technology(略してCET)では関数の先頭にendbr命令を置く。endbr自体は何もしない命令だが、callやjmpで飛んだ先の最初の命令がendbrでない場合、CPUは例外を発生させる。これによって関数は必ず先頭から実行しなければならなくなり、COPが難しくなる。
CETでCOPは難しくなるが、攻撃者が欲しい関数そのものが実行可能バイナリの中に転がっている場合、攻撃者は間接関数呼び出しでその関数を呼ばせるだけで攻撃が成立する。そこで間接関数呼び出しにもう一段階縛りを設けて攻撃を困難にするのがCFI

太古の昔、攻撃者はスタックを壊せるバグを起点としてメモリ上に実行したいコードを置いてスタック上のリターンアドレスを書き換える事でそのコードを実行させる事で任意のコードを実行していた。こうした攻撃を防ぐために現代のOSの下では殆どのメモリは「書けるけど実行できない」か「実行できるけど書けない」設定がなされる。
こうした設定がなされていても、リターンアドレスを既存のコードの好きな位置に書き換える事は出来るため、既存の実行可能バイナリの一部を継ぎ接ぎして任意のコードを実行するReturn-oriented programming(略してROP)が行われるようになった。
ROPを防ぐために現代のx86_64 CPUにはShadow Stackが備わっている。Shadow Stackは関数呼び出し時にスタックに積んだリターンアドレスを書き換え不能な別のスタックにも積んでおき、return時に2つのスタックの値が一致しているかをチェックする。

Adding kernel control-flow-integrity checking to GCC : gccでkcfiを使えるようにしたKees Cook氏によるgccのkcfiについての発表のまとめ記事。Control-flow integrity略してCFIは間接関数呼び出しに細工をして意図しない関数の呼び出しを防ぐ為の仕組み lwn.net/Articles/1098670/

JSConf JP 2026で登壇することになりました。11月22日(日)、東京のベルサール神田で開催されます。

タイトルは「Oseo:SBCLスタイルのJavaScript/TypeScript AOTコンパイラ」。SBCLやChez Schemeのように、動的型付き言語でありながらプログラム全体を事前にネイティブコードへコンパイルするJavaScriptエンジンを作っている、その話をします。TypeScriptの型注釈は最適化のヒントとして使うだけで、決して鵜呑みにしない。ヒントが嘘でも動作は変わらない。そんな設計の話です。

発表は日本語、30分枠、14:10からトラックCです。詳細はこちら。

https://jsconf.jp/2026/ja/talks/hong-minhee

会場でお会いできたら嬉しいです。

jsconf.jp

Oseo:SBCLスタイルのJavaScript/TypeScript AOTコンパイラ | JSConf JP 2026

Common LispのSBCLやSchemeのChez Schemeは動的型付き言語でありながら、プログラム全体を事前にネイティブコードへコンパイルし、何十年もプロダクションで使われてきました。一方JavaScriptは今なお、速く動くにはインタプリタと複数段のJIT、その上に載るデオプティマイゼーション機構が要る言語だと見なされています。この違いはどこから来るのか。この問いから始まったプロジェクトがOseoです。 Oseoはすべての関数を二重にコンパイルします。言語のフルセマンティクスを実装するジェネリックパスが一つあり、値の種類や形状に対して安価なランタイムガードを掛けられる箇所には、その隣に特殊化されたパスを並べます。ガードが失敗すれば、バイナリの中にすでにコンパイルされているジェネリックパスへ分岐するだけです。インタプリタへ落ちることも、デオプティマイズすることもありません。TypeScriptの型表記やJSDocはこの特殊化パスを選ぶヒントとして使われますが、決して鵜呑みにされることはありません。`any`や型アサーション、素のJavaScriptとの相互運用のせいで`: number`という表記はいつでも誤りうるので、Oseoはその可能性をガードで防ぎます。 この発表では、このアイデアが実際にどう動くのかをコードとバイナリで直接お見せします。Node.jsもDenoも使わず、V8が一切リンクされていない純粋なネイティブ実行ファイルがどう作られるか。`--dump-mir`と`--emit-c`で覗いた生成コードの中で、一つのガードが実際にどんな比較と分岐にコンパイルされるか。型ヒントが実行結果を変えてはならないという原則も、同じ領域にある別プロジェクトとの具体的な比較を通じて見ていきます。 Oseoはまだ完成したプロジェクトではありません。この発表は、これまでに証明できたことと、まだ残っている道のりを一緒にお見せする場でもあります。ネイティブI/O、ガベージコレクタ、セルフホスティングまで、システムプログラミングに関心がある方なら貢献できる余地がまだたくさんあります。