random_deviceの実装(再訪)

いっときの気の迷いで作って放置したブログFaith and Brave氏からコメントをいただいていた。

最新のlibstdc++のrandom_deviceの実装を眺めると、可能ならばデフォルトでRDRAND命令を使うようになった点が目を引く(random_deviceとRDRAND命令についてはこちらが詳しい)。また、UNIXまたはUNIXに似た環境ではfreadではなくreadを使うようになった。さて、そもそもの疑問は「freadが必ず成功するという保証はどこで与えられているのか」だった。freadにせよ、readにせよ、エラーチェックはあいかわらずない。

C/C++の仕様の範囲の外、UNIXの仕様の範囲の内を考慮すれば、readが成功しない場合の最たるものはEINTRを喰らった場合だろう。

#include <signal.h>
#include <unistd.h>
#include <iostream>
#include <random>

extern "C" void handler(int) {
  write(1, "handle\n", 7);
}

int main(int argc, char* argv[]) {
struct sigaction sa {};
sa.sa_handler = handler;
sigaction(SIGTERM, &sa, nullptr);

std::random_device random_device {"/dev/random"};
while (true) {
std::cout << random_device() << "\n";
}
return 0;
}

このコードを、RHEL7の標準のGCC、すなわちGCC 4.8.2でコンパイルして動かした。やがてエントロピーが枯渇する。エントロピーが供給されるまでのあいだ、random_device::operator()の内部でブロックする。この瞬間に、killコマンドでSIGTERMを投げつける。readはなにも呼びださないまま、終了する。私が試した環境では、常に0xFFFFを返すようになった。内部的な自動変数の初期化のはなしだから、まあ、なにを返すかに特に意味はない。

世界の頭の良い奴らが、私が気づくレベルのことに気づかないはずはない。検索してみるとlibc++のメーリングリストで議論されていた。最新のlibc++のrandom_deviceの実装を読むと、ちゃんとエラーチェックをしているし、EINTRを喰らったら再度readするようになっている。

最後に、これがバグだったり、セキュリティホールだったりするのか、というと良く判らない。シグナルは鬼門だし、そもそもsigprocmaskするかSA_RESTARTしておけよ、という気もする。やれやれ。