概要
Go言語のuuid生成で有名な satori/go.uuid はこちらのコミットでUUIDv4を生成する際にエラーを返すように変わりました。
理由は
このissueに対応するためなのですが、内部で使っているrand.Read()はそもそもどんな時にエラーが起きるのだろう?と気になって調べてみました。
環境
rand.Reader
ドキュメントにはこう書いてあります。
Reader is a global, shared instance of a cryptographically secure random number generator.
On Linux and FreeBSD, Reader uses getrandom(2) if available, /dev/urandom otherwise. On OpenBSD, Reader uses getentropy(2). On other Unix-like systems, Reader reads from /dev/urandom. On Windows systems, Reader uses the CryptGenRandom API. On Wasm, Reader uses the Web Crypto API.
ref: rand package - crypto/rand - pkg.go.dev
つまりLinuxの場合getrandom
を使用し、それが使えなかったら/dev/urandom
から直接取得するとのこと。
ちょっと用語が増えてきたので、必要な前提知識を先に説明します。
乱数とエントロピープールの話
Linuxでは乱数を生成するデバイスファイル/dev/random
と/dev/urandom
の2つが用意されています。
乱数はエントロピープールから取り出した乱数種(ランダムシード)を元に生成します。
名前が非常によく似ているこの2つのデバイスファイルは、エントロピープールが枯渇した時の挙動が異なります。
エントロピープールとは
Linuxカーネルの保持するバッファで複数の環境ノイズ源から乱雑な数値を集めたものです。 環境ノイズ源には、
- 入力デバイスの発生イベント種別や間隔
- 割り込み発生間隔
- ディスクシーク時間
などがあります。
$ cat /proc/sys/kernel/random/entropy_avail
とすればエントロピープールの貯蓄状態を確認できます。
何回か叩けば値が変わることが分かりますが、これは前述した入力デバイスの間隔をノイズ源として増えています。
/dev/random
エントロピープールは有限で、
$ cat /dev/random
で乱数を延々吐き出すと枯渇して処理をブロックしてしまいます。
このように十分なエントロピーがないとちょっと出力しただけで止まってしまいます。
また/dev/random
ではセキュリティの観点上一度使ったエントロピーは再利用されません。
/dev/urandom
/dev/urandom
は過去に使った乱数種を再利用することで処理を続けます。
つまり/dev/random
と違って延々と乱数文字列を吐き出し続ける事が可能です。
$ cat /dev/urandom
と入力すると延々と乱数が生成されます。
もちろん利便性は高いですが、同じ乱数種を利用するということはランダムでは無いとも言えます。
なので真の乱数ではなく擬似乱数的に生成されていると言われます。
基本的にこちらはブロックしませんが、ブート時などはエントロピープールの初期化が完了していない場合はブロックされることもあります。
getrandom
システムコールの1つです。
/dev/random
または/dev/urandom
からデータを読み出すことと同じ機能で、デフォルトでは/dev/urandom
から読み出します。オプションで/dev/random
からも引き出せます。
わざわざシステムコールとして用意されてるのはなぜ?
/dev/random
や/dev/urandom
はデバイスファイルであるため、使用する際にはopen
システムコールで開く(=ファイルディスクリプタを割り当てる)必要があります。
なのでファイルディスクリプタを限界まで使用済みの状態では新たにファイルを開けないため、これら乱数の読み出しも不可能になります。
そうならないようシステムコールとして追加されました。
どんな時にエラーが起きるのか
話を戻します。
getrandom
を使用し、それが使えなかったら/dev/urandom
から直接取得
なので、これらがエラーを返した時にrand.Reader
はエラーを返す事が分かります。
getrandomの時
ありうるのはEAGAIN
で、
EAGAIN - The requested entropy was not available, and getrandom() would have blocked if the GRND_NONBLOCK flag was not set.
ref: getrandom(2) - Linux manual page
とあります。つまり要求したエントロピーが利用できないケースですね。
実体は/dev/urandom
なので基本的に枯渇しないですが、ブート時などエントロピープールの初期化が完了していない時は発生しそうです。
/dev/urandomの時
上記のケースに加え、/dev/urandom
を直接使う時はファイルディスクリプタも関わってくるのでファイルディスクリプタが枯渇していればエラーが発生します。
まとめ
Linux環境では基本的にrand.Read()
はエラーを返さないようです。
ただ特定のユースケースでは発生しうるため、用途がはっきりしていれば無視しても問題ないですがそうでない場合はちゃんとエラーハンドリングしましょう。
その他
satoriは長期間メンテされてないので使っている方は
へのリプレースをオススメします。