ruby-changes:20081
From: nahi <ko1@a...>
Date: Thu, 16 Jun 2011 23:24:42 +0900 (JST)
Subject: [ruby-changes:20081] nahi:r32128 (ruby_1_8_7): backport r32050 by akr
nahi 2011-06-16 23:22:36 +0900 (Thu, 16 Jun 2011) New Revision: 32128 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=32128 Log: backport r32050 by akr * lib/securerandom.rb (SecureRandom.random_bytes): modify PRNG state to prevent random number sequence repeatation at forked child process which has same pid. reported by Eric Wong. [ruby-core:35765] backport r32124 by nahi * test/test_securerandom.rb: Add testcase. This testcase does NOT aim to test cryptographically strongness and randomness. It includes the test for PID recycle issue of OpenSSL described in #4579 but it's disabled by default. Added files: branches/ruby_1_8_7/test/test_securerandom.rb Modified files: branches/ruby_1_8_7/ChangeLog branches/ruby_1_8_7/lib/securerandom.rb Index: ruby_1_8_7/ChangeLog =================================================================== --- ruby_1_8_7/ChangeLog (revision 32127) +++ ruby_1_8_7/ChangeLog (revision 32128) @@ -1,3 +1,17 @@ +Thu Jun 16 22:55:02 2011 Hiroshi Nakamura <nahi@r...> + + * test/test_securerandom.rb: Add testcase. This testcase does NOT aim + to test cryptographically strongness and randomness. It includes + the test for PID recycle issue of OpenSSL described in #4579 but + it's disabled by default. + +Mon Jun 13 18:33:04 2011 Tanaka Akira <akr@f...> + + * lib/securerandom.rb (SecureRandom.random_bytes): modify PRNG state + to prevent random number sequence repeatation at forked + child process which has same pid. + reported by Eric Wong. [ruby-core:35765] + Thu Jun 2 18:33:51 2011 URABE Shyouhei <shyouhei@r...> * variable.c (rb_const_get_0): Fix previous change. There were Index: ruby_1_8_7/lib/securerandom.rb =================================================================== --- ruby_1_8_7/lib/securerandom.rb (revision 32127) +++ ruby_1_8_7/lib/securerandom.rb (revision 32128) @@ -50,6 +50,14 @@ def self.random_bytes(n=nil) n ||= 16 if defined? OpenSSL::Random + @pid = $$ if !defined?(@pid) + pid = $$ + if @pid != pid + now = Time.now + ary = [now.to_i, now.usec, @pid, pid] + OpenSSL::Random.seed(ary.to_s) + @pid = pid + end return OpenSSL::Random.random_bytes(n) end if !defined?(@has_urandom) || @has_urandom Index: ruby_1_8_7/test/test_securerandom.rb =================================================================== --- ruby_1_8_7/test/test_securerandom.rb (revision 0) +++ ruby_1_8_7/test/test_securerandom.rb (revision 32128) @@ -0,0 +1,178 @@ +require 'test/unit' +require 'securerandom' +require 'tempfile' + +# This testcase does NOT aim to test cryptographically strongness and randomness. +class TestSecureRandom < Test::Unit::TestCase + def setup + @it = SecureRandom + end + + def test_s_random_bytes + assert_equal(16, @it.random_bytes.size) + 65.times do |idx| + assert_equal(idx, @it.random_bytes(idx).size) + end + end + +# This test took 2 minutes on my machine. +# And 65536 times loop could not be enough for forcing PID recycle. +if false + def test_s_random_bytes_is_fork_safe + begin + require 'openssl' + rescue LoadError + return + end + SecureRandom.random_bytes(8) + pid, v1 = forking_random_bytes + assert(check_forking_random_bytes(pid, v1), 'Process ID not recycled?') + end + + def forking_random_bytes + r, w = IO.pipe + pid = fork { + r.close + w.write SecureRandom.random_bytes(8) + w.close + } + w.close + v = r.read(8) + r.close + Process.waitpid2(pid) + [pid, v] + end + + def check_forking_random_bytes(target_pid, target) + 65536.times do + pid = fork { + if $$ == target_pid + v2 = SecureRandom.random_bytes(8) + if v2 == target + exit(1) + else + exit(2) + end + end + exit(3) + } + pid, status = Process.waitpid2(pid) + case status.exitstatus + when 1 + raise 'returned same sequence for same PID' + when 2 + return true + end + end + false # not recycled? + end +end + + def test_s_random_bytes_without_openssl + begin + require 'openssl' + rescue LoadError + return + end + begin + load_path = $LOAD_PATH.dup + loaded_features = $LOADED_FEATURES.dup + openssl = Object.instance_eval { remove_const(:OpenSSL) } + + remove_feature('securerandom.rb') + remove_feature('openssl.rb') + Dir.mktmpdir do |dir| + open(File.join(dir, 'openssl.rb'), 'w') { |f| + f << 'raise LoadError' + } + $LOAD_PATH.unshift(dir) + require 'securerandom' + test_s_random_bytes + end + ensure + $LOADED_FEATURES.replace(loaded_features) + $LOAD_PATH.replace(load_path) + Object.const_set(:OpenSSL, openssl) + end + end + + def test_s_hex + assert_equal(16 * 2, @it.hex.size) + 33.times do |idx| + assert_equal(idx * 2, @it.hex(idx).size) + assert_equal(idx, @it.hex(idx).gsub(/(..)/) { [$1].pack('H*') }.size) + end + end + + def test_s_base64 + assert_equal(16, @it.base64.unpack('m*')[0].size) + 17.times do |idx| + assert_equal(idx, @it.base64(idx).unpack('m*')[0].size) + end + end + +if false # not in 1.8.7 + def test_s_urlsafe_base64 + safe = /[\n+\/]/ + 65.times do |idx| + assert_not_match(safe, @it.urlsafe_base64(idx)) + end + # base64 can include unsafe byte + 10001.times do |idx| + return if safe =~ @it.base64(idx) + end + flunk + end +end + + def test_s_random_number_float + 101.times do + v = @it.random_number + assert(0.0 <= v && v < 1.0) + end + end + + def test_s_random_number_float_by_zero + 101.times do + v = @it.random_number(0) + assert(0.0 <= v && v < 1.0) + end + end + + def test_s_random_number_int + 101.times do |idx| + next if idx.zero? + v = @it.random_number(idx) + assert(0 <= v && v < idx) + end + end + +if false # not in 1.8.7 + def test_uuid + uuid = @it.uuid + assert_equal(36, uuid.size) + uuid.unpack('a8xa4xa4xa4xa12').each do |e| + assert_match(/^[0-9a-f]+$/, e) + end + end +end + + def protect + begin + yield + rescue NotImplementedError + # ignore + end + end + + def remove_feature(basename) + $LOADED_FEATURES.delete_if { |path| + if File.basename(path) == basename + $LOAD_PATH.any? { |dir| + File.exists?(File.join(dir, basename)) + } + end + } + end + +end -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/