HN 日本語サマリー

← 一覧へ戻る
セキュリティ

random.bytes()は実行されるが機能しない時

When random.bytes() runs but doesn't work (insider.btcpp.dev)

64 pointsby Funes-31 コメント

要約

この記事は、COLDCARDファームウェアにおけるrandom.bytes()関数のバグを調査しています。開発者がコード変更を説明するコミットメッセージの質が低いことが、ハードウェア乱数生成器(RNG)が無効化され、脆弱な乱数生成器が使用される原因となった詳細を解説しています。このバグは、開発者がC言語のコンパイルエラーを誤って修正しようとした結果、意図せずセキュリティ上の重大な問題を引き起こしたことを示しています。

全文翻訳

random.bytes()は実行されるが機能しない時 最近のCOLDCARDバグについてコミットメッセージが語ること Dusty Daemon 2026年8月1日 これは、著名なCore-Lightning開発者であるddustin氏によるゲスト投稿です。同氏はColdcardファームウェアのコミット履歴を調査し、何が起こり、なぜコードが失敗したのかを明らかにしました。 はじめに Coldcardのハッキングについて調査を始めたとき、私はすぐにショックを受けました。その理由を説明する必要があります。 開発者がコードに取り組むとき、コードの変更を「コミット」と呼ばれる変更セットに整理またはコード化します。その目的は、何が変更され、なぜ、どのように変更されたのかを示す明確な履歴を示すことです。 これはまさに、ビットコイン保有者の資金が大量に盗まれているように見える場合に、それがどのようにして起こりうるのかを調査し、理解できるようにするためです。 優れた開発者は、コード変更に付随するメモである明確なコミットメッセージを書きます。これは、特定の変更が何を達成しているのかを説明します。 明確なコミットメッセージを作成するには、通常、コミットがより小さなコード変更を表すようにしたいと考えます。そうすれば、コメントする量が少なくなります。 開発者としての良い目標は、コミットメッセージ対変更コードの比率が高いことです。変更するコードの行数が多いほど、なぜコードを変更しているのかを説明するコメントが多くなります。コミットあたりのメッセージが多く、コードの変更が少ないのが一般的によい考えです。 これは私の自身の作業からランダムに選ばれた例です。 コミットメッセージは235文字で、コミットは15行のコードを変更しています。これは235/15 = 約16の比率です。 Cold cardでは、低エントロピーバグを導入したコミットはここにあります。 コミットメッセージは5文字で、単に「runs」という単語です。コミットは1534行のコードを変更しており、比率は5/1534 = 約0.003です。 これは、コメント対変更コードの比率としてひどく悪いものです。 コメント比率が低いことが正当化されるまれなケースもありますが、コードの最も重要な部分を変更することはそのケースの1つではありません。 プロジェクトのセキュリティに不可欠な関数に触れるコードは、変更に対するコメントの比率が高く、より厳格なレビューが必要です。 Coldcardsの弱いエントロピー問題に寄与した2番目のコミットはここにあります。 コミットメッセージは1文字です。単に「x」という文字です。コミットは約1000行のコードを変更しており、比率は1/1000 = 約0.001です。 問題 「runs」というタイトルのコミット(比率:約0.003)では、カスタムmicropythonコードをすべてのColdcardが実行されているSTM32ボードで動作させるために、Cコードをインポートおよび設定しているようです。 STM32はこのような小型デバイスで最も一般的なCPUであり、このコミットで導入されているような設定は一般的です。 「runs」コミットでは、ハードウェアRNG(乱数生成器)が次のコード行で無効化されました。 #define MICROPY_HW_ENABLE_RNG (0) これがバグの原因でした。この値をゼロに設定すると、デフォルトのmicropython rngコードにハードウェアRNGデバイスを使用しないように指示し、代わりにYasmarang RNGを使用するように指示します。開発者は次のようなインラインコメントを追加しました。「これを説明する」 // We have our own version of this code. COLDCARDのこのコードのバージョンは、rng.hおよびrng.crng.hに追加された関数を参照しているようです。 MP_DECLARE_CONST_FUN_OBJ_0(pyb_rng_get_obj); MP_DECLARE_CONST_FUN_OBJ_1(pyb_rng_get_bytes_obj); これらは、stm32 rngライブラリの`pyb_rng_getobj`関数をオーバーライドしようとした試みであるようです。このアプローチは問題に遭遇しました。stm32 rng.cファイルはすでにpyb_rng_et_obj変数を定義しており、値を`pyb_mg_get`に設定しています。同じ変数を2つ定義してコンパイルすることはできません。 rng.c MP_DEFINE_CONST_FUN_OBJ_0(pyb_rng_get_obj, pyb_rng_get); マクロを展開し、論理的に考えると、これは擬似コードにすぎません。 var pyb_rng_get_obj = pyb_rng_get コミット `37e4af5` はカスタムrng.cファイルを追加し、そこで同じマクロ定義をコピー&ペーストしました。 MP_DEFINE_CONST_FUN_OBJ_0(pyb_rng_get_obj, pyb_rng_get); このコードがコンパイルされる方法はありません。開発者は、変数の重複バージョンを作成することにより、`pyb_rng_get_obj`変数を単純にオーバーライドしようとしているようです。Cはそうは機能しません。このエラーは、「重複シンボルpyb_rng_get_obj」というコンパイラエラーを引き起こしたはずです。なぜなら、stm32ライブラリと新しく追加されたrng.cファイルの両方で定義されているからです。 ここから、私はフラストレーションのあまり、`MICROPY_HW_ENABLE_RNG`を0に設定したと推測します。これにより、コンパイラエラーが解決されたでしょう。 開発者がフラフラしているとき、彼らは何かが役立つかどうかを確認するためにランダムなことを試すことがあります。`MICROPY_HW_ENABLE_RNG`を0に設定すると、コンパイラエラーが***間違った理由で***消えたでしょう。 これは、2つの競合する定義を持つコンパイラエラーを隠蔽しました。 // We have our own version of this code. #define MICROPY_HW_ENABLE_RNG (0) MICROPY_HW_ENABLE_RNGをゼロに設定すると、ハードウェア乱数ジェネレータを使用するコード、またはstm32 rng.cファイルの31行から80行が完全に削除されました。これにより、pyb_rng_get_objの2番目の定義が削除されるという副作用があり、コンパイラエラーが「修正」されました。コンパイラエラーは、プログラマーにロジックを再考するように求めていました。そうなる代わりに、コンパイラエラーは単に沈黙されました。コンパイラは開発者に、やろうとしていることを再考する最後のチャンスを与えていましたが、アラームは無視され、沈黙されました。 これで全てが一致しました。開発者は変数をオーバーライドしようとしたが、定義の競合に遭遇したようです。「重複シンボル」エラーに直面したとき、コードを実行させるためにランダムなことを変更しようとしました。 彼は`MICROPY_HW_ENABLE_RNG`を0に変更するとコンパイルできることを発見しました。彼はなぜそうなるのかおそらく知りませんでしたが、理論を思いつきました。私たちはコードの独自のバージョンを持っているので、もうそれは必要ありません。 彼のpyb_rng_getの定義は配置されており、彼が実行したくないと思っていたコードはオフにされていました。 混乱した呼び出しチェーン ColdcardのファームウェアはCで書かれています。ハードウェアに何をすべきかを指示するアプリケーション層はPythonで書かれています。PythonコードはCコードを呼び出します。開発者はpyb_rng_get関数を上書きしました。残念ながら、多くの人にとって、彼のコードが上書きしたpyb_rng_get関数は、Pythonコードから実際に呼び出されるものではありませんでした。Coldcardのv4.0.0のPythonコードは、make_new_wallet()関数でrandom.bytes()を呼び出します。 async def make_new_wallet(): await ux_dramatic_pause(’Generating...’, 4) seed = random.bytes(32) # OOPS assert len(set(seed)) > 4 seed = ngu.hash.sha256s(seed) await approve_word_list(seed) `MICROPY_HW_ENABLE_RNG`を0に設定すると、STM32ライブラリ提供のハードウェアコードが無効化されましたが、開発者は`pyb_rng_get_obj`を設定できるようになりました。問題は、pyb_rng_get_objはPythonから見えるpyb.rng()呼び出し可能なオブジェクトであることです。 これは、ウォレット関数で開発者が呼び出しているものではありません。代わりに、彼らは`random.bytes(32)`を使用しており、これはpyb_rng_getの呼び出しパスを完全にスキップし、代わりに`rng_get`を呼び出します。`MICROPY_HW_ENABLE_RNG`がゼロに設定されているため、micropython stm32 rng.cのL112の定義を使用し、これは非ハードウェアウォレットエントロピーである安全でないYasmarangを呼び出しました。 uint32_t rng_get(void) { return pyb_rng_yasmarang(); } 簡単に言えば、ファームウェアの変更は、新しいウォレットを作成する際に使用されない関数を上書きする一方で、Pythonメソッドオーバーライドを含めるという副作用により、あらゆるケースでハードウェアRNGの使用をオフにし、代わりに非常に弱い乱数生成器を使用します。 最後の証拠はコミットメッセージ自体です。それは単に「runs」というメッセージでした。 開発者の皆さん、もしこのような状況に陥ったら、どうか止めてください。あなたが受け取っている報酬は、他人に引き起こす可能性のある壊滅的な被害に見合うものではありません。理解していないコードを出荷しないでください。 Micropythonへの呪い 根本的に、これは複雑さのレイヤーが多すぎることの結果であるようです。Micropythonライブラリ、CコードとPythonアプリケーションの間のバインディング、そしてCOLDCARDが追加している新しい関数があります。このパッチを作成した開発者は、実際にはCを書くことを強制されたPython開発者だったのでしょうか? Micropythonは、組み込み開発者がC、CPU、または