getrandomの256バイト境界、初期化待ちと読み残しを分けて扱う

閉じた薄緑のさや形容器、真珠が見える開いた容器、真珠を載せたラベンダー色の段が並ぶ。

Linuxで乱数のバイト列を受け取るgetrandomは、指定した長さの配列を渡せば、いつでも一度で満たしてくれる呼び出しではありません。起動直後に乱数源の準備を待つことと、一部のバイトだけを受け取ることは別の状況です。256バイトという境界も、初期化状態やフラグを含めて読む必要があります。

簡単にまとめると?

  • 既定のurandom源を使うgetrandomは、乱数源の初期化前には待機します。GRND_NONBLOCKを付けると、その状況では待たずにEAGAINを返します。
  • 初期化済みのurandom源で、要求が256バイト以下なら、要求した量を返し、シグナルによるEINTRを生じないとマニュアルは説明しています。大きな要求や別のフラグへ、その条件を拡張しません。
  • 返り値が正なら受け取ったバイト数です。必要量に達するまでの残りを管理し、EINTRやEAGAINをバイト数へ足さないことが、読み残しを防ぐ基本になります。

乱数の内容と、受け取りの完了は別の話

getrandom(buf, size, flags)は、呼び出し側が用意したbufへ、最大sizeバイトの乱数を書き込みます。成功時の返り値は、実際に書き込まれたバイト数です。失敗時は−1となり、errnoで理由を確認します。[1、DESCRIPTION、RETURN VALUE] 要求した長さと、受け取れた長さを別々に持つインターフェースです。

ここで「乱数」と呼んでいるのはバイトの内容の性質です。一方、処理がすべて完了したかどうかは、返り値と呼び出し条件から判断します。バッファを確保しただけでは、そこに要求量の有効なデータが入ったことにはなりません。−1を符号なし整数へ変換して大きな成功数と解釈するような扱いも避ける必要があります。

本稿はLinux man-pages 6.19のgetrandom(2)を読み、主にGRND_RANDOMを付けない既定のurandom源の動作を整理します。乱数生成器の暗号学的な安全性を独自に評価するものではなく、乱数を使う個別の暗号方式や鍵の長さを選ぶ記事でもありません。

初期化待ちを、短い読み取りと混同しない

既定のurandom源がまだ初期化されていないとき、通常のgetrandomは準備が整うまで待機します。GRND_NONBLOCKを指定すれば、その状況で待機する代わりに−1とEAGAINを返します。[1、DESCRIPTION] EAGAINは「今回はこの条件では準備できていない」という制御の分岐であり、要求した乱数がバッファへ届いたことを表しません。

たとえば必要量を32バイトと決めても、要求が小さいことだけを根拠に、起動直後から待たずに32バイトを得られるとはいえません。初期化前か後かという条件が先にあります。非待機でEAGAINが出た場合に、同じ呼び出しを休みなく繰り返せば、待機を避けたつもりでもCPUを使って準備を待つ設計になります。

必要なデータがまだ得られないとき、起動処理を待たせるのか、別の仕事を進めて後で試すのか、上位へ未完了を返すのかはアプリケーションの設計です。EAGAINを成功として扱ったり、未入力部分をそのまま利用したりする理由にはなりません。

256バイトの境界には、前提がある

マニュアルは、初期化済みのurandom源から256バイト以下を要求する場合、要求したバイト数が返り、シグナルハンドラーによってEINTRで中断されないと説明しています。[1、NOTES] 「初期化済み」「urandom源」「256バイト以下」という条件をひとまとめにして読みます。

256バイトを超える要求には、この特別な保証がありません。シグナルによって一部のバイトだけを返す場合や、−1とEINTRを返す場合があります。[1] 257バイトを要求したときに必ず短くなる、という意味ではありません。要求量すべてを得られる可能性もありますが、常にそうなると決めて処理を省略できないという違いです。

また、GRND_RANDOMを指定した場合まで同じ条件を適用してはいけません。ここではそのフラグを使う経路を扱わず、既定の源に範囲を絞ります。小さい要求を複数回に分けるときにも、初期化前の待機やその他のエラーまで消えるわけではありません。戻り値を確認する習慣は残ります。

600バイトを受け取る、二つの帳簿

独自の説明用モデルとして、600バイトの領域を埋める場合を考えます。重要な値は、確保した容量600、すでに受け取った数done、次に要求する数wantです。正の返り値nを得たら、doneをnだけ進め、次の書き込み先を先頭からdoneバイト後へ移します。実際に受け取っていない分まで進めません。

まず、一度に残り全量を要求する系列を作ります。要求600に対して200が返ったら、doneは200、残りは400です。次の要求400でEINTRになれば、今回の呼び出しでは成功したバイト数を得ていないのでdoneは200のまま。再度400を要求して400が返れば、合計600で完了します。この200、EINTR、400という並びは説明用に与えた入力で、実機で観測した頻度や必ず起きる順番ではありません。

もう一つは、初期化後に一回の要求を最大256バイトとする系列です。600を256、256、88に分け、それぞれ要求どおり受け取れば、doneは256、512、600と進みます。小分けによって境界の条件に合わせられますが、呼び出し回数は増えます。速くなると主張するには、別途、対象環境での性能測定が必要です。

600バイトを埋める独自モデル。一括要求では返り値200、EINTR、400に対し完了数200、200、600。初期化後の小分け要求では256、256、88で完了数256、512、600。
返り値と完了位置の関係を整理した独自モデル。シグナルや乱数源を実行して測った結果ではない。

返り値ごとに、次の動作を分ける

この帳簿を使えば、エラー時に何を変えてはいけないかが明確になります。EINTRを受け取った回を成功として数えたり、部分的に受け取ったデータを次の呼び出しで先頭から上書きしたりすると、完了数と実際の内容がずれます。読み取り済みの範囲を保ち、残りの先頭へ書くことが必要です。

ただし、どの失敗でも無条件に繰り返せばよいわけではありません。EAGAINなら待機方針が必要ですし、不正なフラグや無効なアドレスといった問題は、同じ引数を繰り返しても解決しません。マニュアルは、利用するカーネルでこの呼び出しが提供されない場合も区別しています。[1、ERRORS] リトライしてよい理由と、呼び出しを直すべき理由を分けます。

独自モデルでは、上の二系列に加え、開始直後のEAGAINで完了数が0のままであること、残りより大きい成功数を不正として拒むこと、正の要求に対して進捗0を返されたモデル入力を無限反復させないことを照合しました。後二者は呼び出し側の帳簿の防御的な検査であり、その戻り方が通常のgetrandomで発生すると主張するものではありません。

getrandomは、乱数デバイスのパス名や開いたファイル記述子を使わずに呼び出せる点も特徴です。[1、NOTES] それでも、初期化、要求量、返り値、エラーという確認事項は残ります。APIの利便性と、一回の呼び出しが仕事全体を完了した証拠は、分けて扱うと読み残しを防ぎやすくなります。

出典

  1. Linux man-pages, getrandom(2)、man-pages 6.19、ページ日付2026-02-08、HTML作成2026-09-09。DESCRIPTION、RETURN VALUE、ERRORS、NOTESを参照。2026年10月23日確認。600バイトの二系列、帳簿の検査、図は独自作成。