すべてのCコードをRustに変換する技術、TRACTOR
(darpa.mil)- DARPAのTRACTORは、レガシーCコードをRustへ自動変換し、Cプログラムのメモリ安全性に関する脆弱性のカテゴリをなくすことを目指す研究プログラムである
- CとC++のメモリ安全性の問題は20年以上にわたって扱われてきたが、バグ検出ツールだけでは十分でないという認識が形成されている
- 代替策は、コンパイル時に安全でないプログラムを拒否する安全なプログラミング言語へ既存コードを移行することだ
- 変換結果は、熟練したRust開発者が書いたコードと同等の品質とスタイルを目標とし、静的・動的解析と大規模言語モデルベースの機械学習を組み合わせる
- MIT Lincoln Laboratoryが試験・評価を担当し、ベンチマーク、マイルストーンプロジェクト、ツールは公開ページに掲載されている
TRACTORの目標
- TRACTORは「Translating All C to Rust」プログラムであり、レガシーCコードをRustへ自動変換することを目標とする
- セキュリティの観点では、Cプログラムに存在するメモリ安全性に関するセキュリティ脆弱性のカテゴリ全体を取り除こうとしている
- 変換されたコードは単に動作するだけでなく、熟練したRust開発者が作成したコードと同等の品質とスタイルを志向する
なぜRustへの変換が必要なのか
- CとC++のメモリ安全性の問題は、ソフトウェアエンジニアリングコミュニティが20年以上にわたって取り組んできた課題である
- バグ検出ツールのみに依存する方法では、問題を十分に解決するのは難しいという合意が形成されている
- より好ましいアプローチは、コンパイル時に安全でないプログラムを拒否できる安全なプログラミング言語を使うことだ
- TRACTORはその対象言語としてRustを採用している
自動変換に使われる技術
- TRACTORは、レガシーCコードをRustへ移行するために、複数のソフトウェア解析技法と機械学習技法を組み合わせる
-
解析技法
- 静的解析と動的解析をあわせて活用する
- 大規模言語モデルを含む機械学習技法も組み合わせに含まれる
- この組み合わせは、Cコードの構造と動作を把握し、Rustコードへ自動変換するために必要な中核技術群である
試験・評価と公開資料
- MIT Lincoln Laboratoryチームが研究プログラムの試験・評価を実施する
- ベンチマーク、マイルストーンプロジェクト、ツールは MIT Lincoln Laboratory TRACTORページ に掲載されている
- 関連コンテンツとして Eliminating Memory Safety Vulnerabilities Once and For All がリンクされている
1件のコメント
Hacker News のコメント
参考までに、https://sam.gov/opp/1e45d648886b4e9ca91890285af77eb7/view も見る価値があります
このイベントの目的は、TRACTOR の技術目標と課題を説明し、潜在的な提案者からの質問に答え、それぞれの研究が TRACTOR プログラムの目標にどう合致するかを検討する機会を提供することだとされています
これは本当に難しそうです。特に熟練者が書く慣用的な Rustは C とはまったく似ておらず、興味深いコードの多くは C++ で書かれています
結局のところ、C プログラムにおけるすべての割り当ての寿命を静的に判定するのと同じことではないかと思います。ユーザー定義アロケータやプロプライエタリなライブラリへ渡る割り当てまで含めてです。この分野の研究は長く続いてきましたが、大きな成功は多くありませんし、C/C++ プログラムでは参照カウントのような安全策がなくても、ユーザーがどのボタンを押すかに割り当ての寿命を結び付けることができます。良い考えではありませんが、可能ではあります
もう一つの明らかな問題は、解析対象のプログラムが定義上バグのあるコードだという点です。寿命が筋の通ったものだったなら、メモリ安全性の脆弱性も存在せず、置き換える必要もなかったはずです。静的に「寿命がどうあるべきか」を検出する研究は、たいてい解析対象のコードがそもそも正しいことを前提にしています。寿命を判定できない箇所を見つけて、開発者に助けを求めるプログラムを目標にすることはできるかもしれません
ただし DARPA が自動プログラム翻訳や Rust への自動翻訳を試みるのは今回が初めてではありません[2]
[1]: https://www.urbandictionary.com/define.php?term=DARPA%20hard
[2]: https://c2rust.com/
このプロジェクトは、こうした言語を実質的に同じノード型の集合でサポートする統合抽象構文木を持っていて、かなりクールです。2014 年夏に Livermore でインターンをしたときに作業する機会がありました
オープンソースでもあります: http://rosecompiler.org/ROSE_HTML_Reference/index.html
借用チェッカーと格闘するだけでも難しいと感じるなら、C のあり得るすべてのプログラム空間と悪名高い未定義動作まで考慮して、借用チェッカーが承認するコードへ自動翻訳する作業ははるかに難しいです。コンパイラ作成における古典的な問題の一つは、有効なプログラムの空間が、コンパイル可能なプログラムの空間よりはるかに大きいということです
ざっと検索したところ、c2rust のような試みもあります[1]。TRACTOR がどう違うのか気になります
[1] https://github.com/immunant/c2rust
元のプログラムを入れる代わりに、そのプログラムのマニュアルを入れる方式なら、より興味深いでしょう。ただし、マニュアルがプログラムの微妙な挙動をすべて含んでいることはまれなので、少なくとも実際の基準値を得るにはソースコードを覗く必要がある可能性が高いです
この提案されたイニシアチブのことはしばらく前から知っていたので、こうして公開されるのは興味深い。非常に野心的な提案であり、このくらいの野心はDARPAのミッションに合っていると思うので、うまくいってほしい。
この分野でRustを支持する立場として、CからRustへの自動翻訳の可能性について、この提案を推進する人たちの期待値を下げようとしてきた。私が見る根本的な障害は、CのソースコードがRustのソースコードよりも情報量が少ないという点だ。CコードをRustコードに翻訳するには、誰か、あるいは何かが、その欠けている情報を作り出さなければならない。画像を大きく拡大しても、元画像に捉えられていない情報ビットを誤りなく作り出すことはできないのと同じ理由で、この欠けた情報を完全に生成することは不可能だと容易に証明できる。結局、既存のソースコードに欠けている情報を外挿、つまり発明しなければならない。正しく外挿するには判断が必要で、特に教師なしの言語モデルが大量に行う場合には、必然的に誤りが生じるプロセスだ。この問題をある程度緩和できると考える解決策を提案したことはあるが、詳細は述べない。
最終的には、このプロジェクトはある程度の成功を収める可能性があると見ているが、慎重で抑制された期待値を持って進めるべきだ。同時に、公開された成果がまったく出ない可能性もあるため、現時点で過度に解釈しないようにと言いたい。特に政府は単一の存在ではないので、このプロジェクトをCに対する全面的な否定や、Rustに対する全面的な祝福と解釈してはならない。各機関はメモリ安全技術の導入方針とスケジュールを自ら決めるだろう。たとえばNISTはRustだけでなく、Ada SPARKや、複数の強化されたC/C++方言も推奨している。
https://cpp-rust-assisted-migration.gitlab.io/blog/
実際のRust化プロジェクトは、しばしばこのように進む。ファザーも追加のテストデータを作って分岐カバレッジを高める助けになり得る。
個人的には「世界をすべてRustで書き直そう」という考え方は好きではない。とはいえ、あるプロジェクトを新しい言語やプラットフォームへ移植しようとするなら、機械的な翻訳は悪い方法だ。
時間をかけてより良いアーキテクチャを計画し、より良いソフトウェアシステムを設計したうえで、部分ごとに置き換える方法を探すべきだ。空中に城を築いても、決して地面には届かない。このシステムにRustを使うことにしたのならそれはよいが、Rustらしく書くべきだ。CをRustへ逆移植しようとしてはいけない。
はるかに良く成熟したプロセスは、Cを現代的なCへ更新し、CBMCのようなモデル検査器でメモリ、リソース、整数演算の安全性を検証することだと思う。段階的なRustでの再実装と同じ安全性を得ながら、コードベース、知識基盤、開発者を維持できる。
手でアーキテクチャを設計したRust版に置き換えるのが明らかにより良い解決策だという点には同意するが、コストもより大きい。ここではRLBox式に「ほとんど労力をかけずにセキュリティを大きく改善する」製品を目指しているように見える。リソースがあるなら完全な手動での再実装をすべきでないという意味ではなく、やらないよりはましだという話だ。
そもそも解こうとしている問題をChatGPTに聞けば、より頻繁に正解するとはいえ、それでもたいていうまくいかない。出力された基本的なものですら動くようにするには、依然として多くの手直しと思考が必要だ。
明らかに良いアイデアだという魅力ではない。翻訳で作られた「粗末なRust」は、バグという背景ノイズを抱えたCよりも、作業するにははるかに悪い可能性が高い。忠実な翻訳のおかげで、そのバグも「粗末なRust」にそのまま残るだろう。Cでは人々は問題への対処方法をある程度知っているが、「粗末なRust」はRustの人たちがそうしたものに慣れていないため、文字通り粗末なものになる。
それでも、誰かが繰り返し整理して耐えられる状態にする技術を発展させる可能性はゼロではない。また、プロジェクトの非目標成果物として「x、y、zのため、耐えられるRustには翻訳できない」といったリンターのフィードバックが出てくる可能性もある。C開発者がそれを見直し、コードが良いRustへ翻訳可能になったなら、あえて翻訳する理由も減る。
こうした結果が出るなら、ある人たちにとっては、実行可能なCで解法を記述し、「翻訳器/リンター」に壊れていないアプローチへ導かせるほうが簡単かもしれない。こうした前向きな結果はかなり可能性が低いと思うが、ときどきダークホースへの賭けをすることこそ、DARPAの職務内容に近いのではないかと思う。
重要なのは、実際に何度も問題になったことが証明されているメモリ安全性CVEを減らし始めようということだ。
C/C++からRustへ自動かつ信頼性高く翻訳できていたなら、すでに実現していただろうという点には同意する。しかし、より良いアーキテクチャとシステムを計画して部分ごとに置き換えるプロセスのどこかで、人々は「もうCをずっと上手に書けているのだから、メモリ安全性バグはこれ以上作らないのではないか、ここで止めてもよいのではないか」という自信を持つことがあり、実際にそうするだろう。すると6か月後、また泣き笑いするようなバッファオーバーフローCVEが出てくる。
Cを現代的なCへ更新してCBMCで検証するのも莫大な投資だ。その程度のことをするなら、むしろRustへ移行するほうがよい。段階的なRustでの再実装と同じ安全性を得るという話は可能かもしれないが、かなり不確実か、明確な結論からは程遠いように見える。
多くの人がこれを、すべての C と C++ コードを Rust に翻訳せよという要求や命令として読んでいるが、目立つプロジェクト名とは違って、アブストラクトはそうは読めない。互いに関連はしているが、別個の2つの段落がある
第一に、C と C++ は大規模では十分に安全ではない。慎重にプログラミングし、良いツールを使っても、根本的に安全ではない設計のためにあまりにも多くの脆弱性が生まれる。したがって、可能な限り多くのコードを「安全な」言語、特にメモリ安全性を保証する言語へ翻訳するか、新たに書くべきだ
第二に、既存の C コードを Rust に翻訳するソフトウェアに資金を出し、公募するということだ
世界を Rust で書き直そうという合意ではない。安全な言語へ移行しようという合意であり、Rust はその一例で、この移行において Rust をターゲットにするプログラムがあるという意味だ
unsafe構文があるなら、それを使うルールはどうなるのか? ここで使う定義済みのルールセットがなければ、結局出発点に戻るだけだRust にはセーフモードがある。Rust が安全な言語というわけではない。面白いことをするには
unsafeブロックが必要になる。これでは多くは得られない一方で、プログラマにポインタを触らせさえしないガベージコレクション言語はたくさんある。なぜそうした言語は検討されないのか? 理由は性能だ。Rust プログラマたちが性能をそれほど「重視」するから、その言語では根本問題を決して解けない
性能が欲しいのか、安全性が欲しいのか? 両方を手に入れることはできない
これが何らかの自動化方式で動き得るというのは本当に驚きだ。一般的な C プログラムを Rust に行単位で転写することはできない
C プログラムにはポインタとエイリアスが至るところにあり、Rust はそうした概念を明示的に防ぐ。全体を
unsafeで包まない限り、C プログラムを Rust で書き直すには、多くの典型的な構造をより高いレベルで考え直す必要があるすべてを一度に翻訳する必要もない。Rust コンパイラの価値の一つは、LLM に戻して反復できる具体的な情報を大量に与えてくれる点だ
私のスタートアップ grit.io では似た問題に取り組んできており、C -> Rust は近い将来に十分扱えるようになると見ている。確かに簡単ではないが、解決可能な問題だ
ならば、それを一つのサブ問題として扱い、スレッド間でデータがどう共有されるかを検出することを別の問題にできる。後者の場合、多くのプログラムには「スレッド T1 がキュー Q に入れ、スレッド T2 が取り出す」といった暗黙の所有権ルールがあるはずだ。これは「キューに入れると所有権が移転する」と翻訳できる
こうしたルールを検出するのは簡単ではないだろうが、まったく手の届かないことにも見えず、研究プロジェクトとしては十分意味があり得る
unsafeで包み、同一のバイナリにコンパイルしたうえで、等価性を検証し続けながらunsafeを少しずつ外していくことはできないのか?そうすれば、少なくとも可能な限り多くの部分を Rust に持っていき、エンジニアが考え直すべき概念だけを別途扱えるように思う
unsafeの Rust 翻訳は作れる気がするAI が本当に有用で有効なプログラムを作るには、ライフタイムなどを把握しなければならないはずだ。本当にそれをやるなら印象的だろう
非常に厳格なコーディング慣行を守り、コードに独自アノテーションを大量に入れる必要があるサードパーティ製の静的検証ツールを統合すれば、C/C++ でも似た結果を得ることはできる
あるいは、単に Rust を使えばいい
本物の代替手段があるのに、あれほど大きく明白な設計上の欠陥がある言語を、正気で誰が擁護するというのか
これはうまくいきそうにない。C には Rust で大きな変更なしに再現できない抽象化がある
C では、同じポインタを含む別々の2つのデータ構造が、そのポインタに書き込むことがよくある。これは Rust で自明に再現できず、かなり賢い介入が必要になる
だが、よりバグの少ない出力や、Rust の信頼性を生み出すプログラム構造を自動生成するプロセスは得られないだろう
これは自動でなければならないのか? だとすると、C を Rust に自動移植できるプログラムは、必然的に C コード自体を安全にする機能をすべて含んでいなければならないのではないか?
ある C コードは単に「正しさを検証できない」のではなく、「メモリ安全性の面で実際に間違っている」。そうしたコードは人の介入なしには自動翻訳されないだろう。正しい等価コードがないのに、どうして可能だろうか。ツールには「このコードが何をしようとしているのか分からず、実際にしていることはコンパイラとの約束を破っているので、意図したことではないのも分かる。人間よ、助けてくれ」と言う逃げ道が必要だ
理論上、その逃げ道が未定義動作が実際に発生するときだけ使われるようにすることは不可能だ。ライスの定理があるからだ。実務上も、過度に難解なコードを盲目的に翻訳すべきではないので、そうしようとする試み自体が望ましくない可能性が高い
結局、ツールは作業の大半をこなしつつ、人が案内する対話型ツールのようなものになると想像している。そしてその人の案内の結果として、正確に等価なコードではなく、同じ目的を果たすコードが出てくることになるだろう
もし 1) Rust にはメモリバグがなく、2) C を Rust に自動翻訳できるなら、すべてのメモリバグを自動的に修正できることになる
これはほぼ確実に事実ではない。この作業は一般的な場合には完全に不可能である可能性が非常に高い
例えば、すべての動的メモリ割り当てを翻訳時点で決めた固定バッファに置き換え、そのバッファに収まらないすべての入力を拒否すればよい