https://redmine.ruby-lang.org/
https://redmine.ruby-lang.org/favicon.ico?1711330511
2017-09-10T02:59:35Z
Ruby Issue Tracking System
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=66572
2017-09-10T02:59:35Z
kosaki (Motohiro KOSAKI)
kosaki.motohiro@gmail.com
<ul></ul><p>小崎です<br>
このへんあんまり詳しくないんですが。</p>
<blockquote>
<p>1.Random.urandom は、getrandom(2) や (/dev/urandom に対する) read(2) システムコールを 1 回しか呼ばないようです。よって、要求量が大きくて 1 回では文字列を準備できないとき(例えば<br>
Random.urandom(100_000_000) )、失敗して nil を返します。これは意図的でしょうか。要求量が得られるまで(または他の要因で失敗するまで)繰り返し呼ぶべきではないでしょうか。</p>
</blockquote>
<p>そんな気がします。一瞬エントロピー枯渇について心配しましたが、Rubyの側で小さいurandomを大量に呼べばどうせ枯渇するんだし</p>
<blockquote>
<p>2.Random.urandom は失敗するとき、nil ではなく例外を投げるほうがよいのではないでしょうか。nil を返して実行が進んで幸せになるケースはあまり思い浮かびませんでした。</p>
</blockquote>
<p>同意します<br>
どうせ実アプリケーションでurandom直接呼んでる人なんていないのだから気にせず変えてしまっていいと思います</p>
<blockquote>
<p>4.securerandom は、初回の呼び出しで Random.urandom が成功したらその後ずっと Random.urandom を使い、失敗したらずっと openssl を使うようになっています。<br>
これは本当に初回の呼び出しだけで振り分けるのでよいのでしょうか。具体的には、Random.urandom(0) は常に成功する(空文字列を返す)ので、実際には urandom が<br>
使えない環境でも urandom に固定される可能性があります。<br>
逆に、securerandom のバックエンドを動的に切り替えるようにしたら問題あるでしょうか。最初は成功していた Random.urandom が途中から失敗するようになったとき、<br>
openssl へフォールバックしても良いものでしょうか。また、Random.urandom が復活した場合、openssl から Random.urandom に戻すのは問題ないでしょうか。</p>
</blockquote>
<p>途中でurandomが使えなくなったり使えるようになったりってのは、1の長大な引数のケースぐらいしか思いつかないので、<br>
固定でいいのではないでしょうか。<br>
それはそれとしてRandom.urandom(0) の件はバグじゃないのかと思います。</p>
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=66581
2017-09-10T12:24:24Z
shyouhei (Shyouhei Urabe)
shyouhei@ruby-lang.org
<ul></ul><p>kosaki (Motohiro KOSAKI) wrote:</p>
<blockquote>
<blockquote>
<p>1.Random.urandom は、getrandom(2) や (/dev/urandom に対する) read(2) システムコールを 1 回しか呼ばないようです。よって、要求量が大きくて 1 回では文字列を準備できないとき(例えば<br>
Random.urandom(100_000_000) )、失敗して nil を返します。これは意図的でしょうか。要求量が得られるまで(または他の要因で失敗するまで)繰り返し呼ぶべきではないでしょうか。</p>
</blockquote>
<p>そんな気がします。一瞬エントロピー枯渇について心配しましたが、Rubyの側で小さいurandomを大量に呼べばどうせ枯渇するんだし</p>
</blockquote>
<p>昔のLinuxのmanに "Users should be very economical in the amount of seed material that they read from /dev/urandom" とか書いてあったのがループしない経緯ではないかと想像しています。その記述はなくなったので、今となってはループしない理由はないんではないかなと思いますがいかがでしょうか。</p>
<p>(まあループの中でエントロピー吸い出すなら当然ブロッキングIOということになるわけで、GVL放した方がいいんじゃないのとか思わなくもないのでパッチはやや大きそうではあると感じますが)</p>
<blockquote>
<blockquote>
<p>2.Random.urandom は失敗するとき、nil ではなく例外を投げるほうがよいのではないでしょうか。nil を返して実行が進んで幸せになるケースはあまり思い浮かびませんでした。</p>
</blockquote>
<p>同意します<br>
どうせ実アプリケーションでurandom直接呼んでる人なんていないのだから気にせず変えてしまっていいと思います</p>
</blockquote>
<p>Random.urandomという名前になったのは2.5からなので、今ならまだ実アプリケーションがどうとか悩む必要もなく、いきなり変えてOKのはずです。</p>
<blockquote>
<blockquote>
<p>4.securerandom は、初回の呼び出しで Random.urandom が成功したらその後ずっと Random.urandom を使い、失敗したらずっと openssl を使うようになっています。<br>
これは本当に初回の呼び出しだけで振り分けるのでよいのでしょうか。具体的には、Random.urandom(0) は常に成功する(空文字列を返す)ので、実際には urandom が<br>
使えない環境でも urandom に固定される可能性があります。<br>
逆に、securerandom のバックエンドを動的に切り替えるようにしたら問題あるでしょうか。最初は成功していた Random.urandom が途中から失敗するようになったとき、<br>
openssl へフォールバックしても良いものでしょうか。また、Random.urandom が復活した場合、openssl から Random.urandom に戻すのは問題ないでしょうか。</p>
</blockquote>
<p>途中でurandomが使えなくなったり使えるようになったりってのは、1の長大な引数のケースぐらいしか思いつかないので、<br>
固定でいいのではないでしょうか。</p>
</blockquote>
<p>0の件は後述。Random.urandomがきちんとループして要求されたバイト数まで読むようになった場合、Random.urandomの実行結果は以下の2パターンのいずれかになるはずです。</p>
<ul>
<li>成功して、必要なだけのデータが入手できる</li>
<li>失敗して、例外が上がる</li>
</ul>
<p>さて、この整理が済んだ後、urandomが「折に触れて成功したり失敗したりする」というのは考えられるでしょうか。なんか失敗する時はずっと失敗するのではないかという気がします。なので、バックエンドの切り替えが固定かどうかはさほど重要な論点ではなくなる気がしています。</p>
<blockquote>
<p>それはそれとしてRandom.urandom(0) の件はバグじゃないのかと思います。</p>
</blockquote>
<p>これに関してはまず、urandom(0)で何が返るべきかですが、空文字列だと思います。</p>
<p>同様の例としてread(0)で何が返るべきかという問題が「APIデザインケーススタディ」の21ページ付近に載っているので読むといいと思うんですが、要は可変長の文字列が欲しいという場合がありえて、意味のある引数として0が渡ってくる可能性がある。そこで戻り値にnilを返しうることにしてしまうと、呼び出し側でnilかどうか毎回チェックせねばならず面倒なわけです。なのでこれは食う文字列であるべき。</p>
<p>一方で、そうすると、初回の引数が0だったときにSecureRandomが意図しない動きになってしまうじゃないかという話はあって、これは回避する必要がありそうです。思いつくのは、</p>
<ul>
<li>たとえ引数が0であってもシステムコールやデバイスファイル読み出しを律儀に行い、きちんと例外をあげていく</li>
<li>urandomとは別に現在利用可能な乱数元の一覧を得るAPIを新設する</li>
</ul>
<p>くらいですが、後者はやや話が大きいので、前者かなあと思います。</p>
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=66583
2017-09-10T12:43:01Z
shyouhei (Shyouhei Urabe)
shyouhei@ruby-lang.org
<ul><li><strong>Related to</strong> <i><a class="issue tracker-1 status-5 priority-4 priority-default closed" href="/issues/9569">Bug #9569</a>: SecureRandom should try /dev/urandom first</i> added</li></ul>
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=66586
2017-09-10T14:52:12Z
mame (Yusuke Endoh)
mame@ruby-lang.org
<ul></ul><p>shyouhei (Shyouhei Urabe) wrote:</p>
<blockquote>
<p>(まあループの中でエントロピー吸い出すなら当然ブロッキングIOということになるわけで、GVL放した方がいいんじゃないのとか思わなくもないのでパッチはやや大きそうではあると感じますが)</p>
</blockquote>
<p>/dev/urandom はブロックしない(少なくとも最近の OS なら)、という前提があっても、GVL 放すべきですかね?</p>
<blockquote>
<p>なんか失敗する時はずっと失敗するのではないかという気がします。</p>
</blockquote>
<p>小崎さんと卜部さんの 2 人がそういうなら、大丈夫な気がしてきました。</p>
<blockquote>
<ul>
<li>たとえ引数が0であってもシステムコールやデバイスファイル読み出しを律儀に行い、きちんと例外をあげていく</li>
</ul>
</blockquote>
<p>これはちょっと安心できない気がします。0 バイトの読み出しに対して何もせずに成功を返すというのは Ruby に限らずありがちな実装だと思うので、getrandom(2) システムコールが同じことをしても驚かないです(仮に今の実装がそうなっていなくても、man などで明記されていない限り、将来的に変わるかもしれない)。</p>
<p>Random.urandom(0) の挙動は今のまま変えず、securerandom は初回の読み出しで(要求が 0 バイトであろうとも)必ず 1 バイト以上読み出してみる、というのが手軽な対策だと思ってます。異論なければ以下のパッチをコミットしようと思いますが、どうでしょうか。</p>
<pre><code>diff --git a/lib/securerandom.rb b/lib/securerandom.rb
index 6a5720c44e..e20591a64f 100644
--- a/lib/securerandom.rb
+++ b/lib/securerandom.rb
@@ -52,7 +52,7 @@ def bytes(n)
end
def gen_random(n)
- ret = Random.urandom(n)
+ ret = Random.urandom(1)
if ret.nil?
begin
require 'openssl'
@@ -67,10 +67,6 @@ class << self
end
return gen_random(n)
end
- elsif ret.length != n
- raise NotImplementedError, \
- "Unexpected partial read from random device: " \
- "only #{ret.length} for #{n} bytes"
else
@rng_chooser.synchronize do
class << self
</code></pre>
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=66591
2017-09-11T01:29:07Z
shyouhei (Shyouhei Urabe)
shyouhei@ruby-lang.org
<ul></ul><p>mame (Yusuke Endoh) wrote:</p>
<blockquote>
<p>shyouhei (Shyouhei Urabe) wrote:</p>
<blockquote>
<p>(まあループの中でエントロピー吸い出すなら当然ブロッキングIOということになるわけで、GVL放した方がいいんじゃないのとか思わなくもないのでパッチはやや大きそうではあると感じますが)</p>
</blockquote>
<p>/dev/urandom はブロックしない(少なくとも最近の OS なら)、という前提があっても、GVL 放すべきですかね?</p>
</blockquote>
<p>それってO_NBLOCKのディスクリプタをビジーループで読み込みってことですよね。ユーザーランドからへんにビジーループするよりカーネル側で適切に生成されてくるのをユーザーランド側はすなおにブロッキングIOしたほうがトータルのコンテキストスイッチが少なそうな気がするんですが、まあ気がするだけで定量的評価ではないです。</p>
<blockquote>
<blockquote>
<ul>
<li>たとえ引数が0であってもシステムコールやデバイスファイル読み出しを律儀に行い、きちんと例外をあげていく</li>
</ul>
</blockquote>
<p>これはちょっと安心できない気がします。0 バイトの読み出しに対して何もせずに成功を返すというのは Ruby に限らずありがちな実装だと思うので、getrandom(2) システムコールが同じことをしても驚かないです(仮に今の実装がそうなっていなくても、man などで明記されていない限り、将来的に変わるかもしれない)。</p>
<p>Random.urandom(0) の挙動は今のまま変えず、securerandom は初回の読み出しで(要求が 0 バイトであろうとも)必ず 1 バイト以上読み出してみる、というのが手軽な対策だと思ってます。異論なければ以下のパッチをコミットしようと思いますが、どうでしょうか。</p>
</blockquote>
<p>これは特に反対ありません。</p>
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=66604
2017-09-11T12:46:09Z
mame (Yusuke Endoh)
mame@ruby-lang.org
<ul></ul><p>shyouhei (Shyouhei Urabe) wrote:</p>
<blockquote>
<p>これは特に反対ありません。</p>
</blockquote>
<p>ありがとうございます。r59840 でコミットしました。(コミットログにチケット番号書き忘れた……)</p>
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=66658
2017-09-14T11:40:33Z
mame (Yusuke Endoh)
mame@ruby-lang.org
<ul></ul><p>特に反対もなかったので、nil を返すのではなく RuntimeError を投げるようにしました(r59858)。</p>
<p>残るはシステムコールをループする点だけですが、</p>
<blockquote>
<p>それってO_NBLOCKのディスクリプタをビジーループで読み込みってことですよね。ユーザーランドからへんにビジーループするよりカーネル側で適切に生成されてくるのをユーザーランド側はすなおにブロッキングIOしたほうがトータルのコンテキストスイッチが少なそうな気がするんですが、まあ気がするだけで定量的評価ではないです。</p>
</blockquote>
<p>これはちょっとよくわかりませんでした。ブロッキング IO にしても、getrandom(2) や read(2) システムコールは途中で終わる可能性がある(つまりカーネル側で要求量きっかり生成してくれることは保証されない)ので、ユーザランドでのループはどのみち必要です。</p>
<p>GVL を放すかどうかは、放してもいいとは思いますが、</p>
<ul>
<li>getrandom(2) などがブロックすることはない(はず)</li>
<li>普通のユースケース(数十バイト程度の urandom 取得)では、ループが必要になることもほぼない</li>
</ul>
<p>ということで、オーバーヘッドになるだけのような気はしてます。<br>
あと、この辺の関数はスレッド周りが初期化されていないプロセス起動時にも呼ばれるので、rb_thread_call_without_gvl が単純に呼べず、さらにパッチがでかくなりそうでした。</p>
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=68454
2017-12-16T00:43:28Z
mame (Yusuke Endoh)
mame@ruby-lang.org
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Closed</i></li></ul><p>開発者会議で議論し、GVL 開放せずにループで良いだろうということになりました。<br>
そしてさきほど r61292 で修正したので閉じます。(コミットログに書き忘れました。すみません)</p>
Ruby master - Bug #13885: Random.urandom と securerandom について
https://redmine.ruby-lang.org/issues/13885?journal_id=96512
2022-02-16T05:19:31Z
mame (Yusuke Endoh)
mame@ruby-lang.org
<ul></ul><p>いまは Random.urandom は利用できない環境で RuntimeError を投げるようになっているはずなのですが、securerandom.rb で Random.urandom が利用可能かをチェックするコードが古いまま(nil を返すかどうかを見る)になってました。</p>
<p>ちょっと Random.urandom が利用できない環境を作るのが難しそうでテストできないのですが、おそらく動かないと思うので修正しておこうと思います。</p>
<p><a href="https://github.com/ruby/ruby/pull/5557" class="external">https://github.com/ruby/ruby/pull/5557</a></p>