RAND_bytes() failed は一つのローカル原始関数についての文であり、アカウント、事前共有鍵、リモートポートについての文ではない。まったく異なる二つの障害が同じ行を生み、残す証拠も異なる。一方はバイトの出力を拒む暗号バックエンドであり、他方はそのコードが前提としたエントロピー源に届かないプロセスである。両者を切り分けるには、エラーキューの読み取り一回と、閉じ込められたプロセスのファイルシステムビューの一瞥一回が要る。そしてその確認は /dev の下に何かを作る前に済ませるべきだ。

戻り値が区別するもの

文書化された契約は二つではなく三つの結果を区別する。成功なら 1、読み込まれた RAND メソッドが要求を支えないなら -1、その他の失敗なら 0 で、説明はエラーキューに委ねられる。RAND_bytes のリファレンスは見落としやすい点を一つ加える:対応プラットフォームでは既定の生成器がオペレーティングシステムから種を得て、その種入れに失敗すると予測可能なバイトを返す代わりにエラー状態に留まる、という点だ。

その数字のうち環境を語るのは一つだけだ。-1 はビルド、プロバイダー、機能の問いであり、コードが有効な実装の提供しないものを要求したからだ。種入れの不満を伴う 0 は環境の問いであり、生成器がエントロピーをまったく得られなかったからだ。数字だけではどちらか分からないし、関数名でも分からない。

隠れたデバイスノードが本当の原因であるとき

制限されたサービスは、あなたが打ち込むシェルとは別の木を見る。chroot の jail、コンテナイメージ、堅牢化された unit ファイルは、ホストより少ないノードを持つ /dev を日常的に見せる。失敗する実装が /dev/urandom をパス名で開くなら、その違いだけで起動は壊れ、ホスト自体はきわめて健全に見える。jail の外に存在するデバイスは、閉じ込められたプロセスが何を開けるかについて何も証明しない。

とはいえ、すべての乱数インターフェースが経路を要するわけではない。Linux の getrandom システムコールはファイル名もファイル記述子も受け取らず、そのマニュアルページは、この性質が chroot のデバイス経路を隠す場面でこそ有用だと述べている。

つまり欠けたノードという説明は、コードの一つの分岐についての仮説であって、乱数一般についての規則ではない。証拠が経路を名指すときは成り立ち、証拠が別の何かを名指すときは推測にすぎない。

まずエラーキューを読み、次にプロセスの見え方

問いを決着させる最短の道は、ライブラリが既に記録した内容を読み、そのうえで jail の内側の見え方を直接確かめることだ。まず サービスが実際に読み込んだ OpenSSL のビルドから始める。プロバイダーのスタックはメジャーバージョン間でもディストリビューション間でも異なるからだ。次に systemd に、どのプロセスが動いていてどんな制限が課されているかを尋ねる。両方の事実が卓上に載って初めて、そのプロセスが見えるノードを列挙する価値が出る。

openssl version -a
systemctl show openvpn-server@lab -p MainPID -p PrivateDevices -p DevicePolicy
pid=$(systemctl show -p MainPID --value openvpn-server@lab)
sudo ls -l /proc/"$pid"/root/dev/urandom /proc/"$pid"/root/dev/random

その読み取りは同じプロセスに対して行い、たまたま開いているシェルに対して行わない。ホストの /dev を無関係なプロセスの見え方と比べるのは、誤った依存を二度直すやり方だ。

名前を言えるようになってから依存を足す

証拠が本当に経路を名指すなら、修正は小さい。その一つの経路だけを jail の内側で使えるようにし、他はそのままにする。単一ノードの bind マウントで足りることが多く、マウントが選べない環境では mknod とノードの標準の major・minor 番号で同じ結果が得られる。サンドボックスを広げ、すべてのキャラクターデバイスに権限を与え、PrivateDevices を切るのは、狙いを定めた修正を恒久的な安全後退と引き換えにすることだ。

エントロピーデーモンはその修正の代わりにならない。haveged のようなパッケージが変えるのはカーネルプールの供給方法であり、閉じ込められたプロセスが開ける経路を作ることはできない。一つ入れると健全性のグラフは良く見えるかもしれないが、open の呼び出しは依然として失敗する。二つの問題を切り離しておかないと、次の障害はまた誤った層に帰される。

生成器とトンネルを別々に検証する

再起動だけが、障害を生んだのと同じ設定、同じ unit ファイル、同じ隔離をそのまま使う試験だ。実行し、それから以前の起動からの見本ではなく、ジャーナルの最新の記録を読む。

sudo systemctl restart openvpn-server@lab
sudo journalctl -u openvpn-server@lab --since -5min --no-pager | grep -iE 'rand|entropy|Initialization Sequence'

その出力で二つの別のものを見る。乱数源についてのメッセージが消えること、そしてトンネルがいつもの起動の節目に届くことだ。再び動く生成器は局所的な障害を一つ取り除くだけで、サーバーの身元、資格情報、経路制御については何も語らない。以前に失敗したのと同じクライアント設定で試し、結果が単に良いのではなく比較できるようにする。

関連記事