summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2011-06-16 14:22:36 +0000
committernahi <nahi@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2011-06-16 14:22:36 +0000
commiteaf6ad66ab413765dd8d92e4fb3fcd06f138109e (patch)
tree40ecc3b485034be0fea6ccdbb33a42065fa80c56
parent61ac67c14b03d52826b08ce89bb03a188dbfd6c3 (diff)
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. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_8_7@32128 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog14
-rw-r--r--lib/securerandom.rb8
-rw-r--r--test/test_securerandom.rb178
3 files changed, 200 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 92b1334eb7..99d5fded70 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+Thu Jun 16 22:55:02 2011 Hiroshi Nakamura <nahi@ruby-lang.org>
+
+ * 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@fsij.org>
+
+ * 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@ruby-lang.org>
* variable.c (rb_const_get_0): Fix previous change. There were
diff --git a/lib/securerandom.rb b/lib/securerandom.rb
index 3fd63e535f..a957687a82 100644
--- a/lib/securerandom.rb
+++ b/lib/securerandom.rb
@@ -50,6 +50,14 @@ module SecureRandom
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
diff --git a/test/test_securerandom.rb b/test/test_securerandom.rb
new file mode 100644
index 0000000000..d0ee254e62
--- /dev/null
+++ b/test/test_securerandom.rb
@@ -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