diff options
-rw-r--r-- | Bugzilla/Constants.pm | 2 | ||||
-rw-r--r-- | Bugzilla/Install/CPAN.pm | 17 | ||||
-rw-r--r-- | Bugzilla/Install/Requirements.pm | 32 | ||||
-rw-r--r-- | Bugzilla/RNG.pm | 233 | ||||
-rw-r--r-- | Bugzilla/Util.pm | 2 | ||||
-rw-r--r-- | mod_perl.pl | 10 |
6 files changed, 259 insertions, 37 deletions
diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 32c74e920..12cbef3c4 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -515,7 +515,7 @@ use constant DB_MODULE => { }; # True if we're on Win32. -use constant ON_WINDOWS => ($^O =~ /MSWin32/i); +use constant ON_WINDOWS => ($^O =~ /MSWin32/i) ? 1 : 0; # True if we're using ActiveState Perl (as opposed to Strawberry) on Windows. use constant ON_ACTIVESTATE => eval { &Win32::BuildNumber }; diff --git a/Bugzilla/Install/CPAN.pm b/Bugzilla/Install/CPAN.pm index a3f913702..31bd7f88f 100644 --- a/Bugzilla/Install/CPAN.pm +++ b/Bugzilla/Install/CPAN.pm @@ -71,13 +71,6 @@ use constant REQUIREMENTS => ( # we make it a constant. use constant BZ_LIB => abs_path(bz_locations()->{ext_libpath}); -# These modules are problematic to install with "notest" (sometimes they -# get installed when they shouldn't). So we always test their installation -# and never ignore test failures. -use constant ALWAYS_TEST => qw( - Math::Random::Secure -); - # CPAN requires nearly all of its parameters to be set, or it will start # asking questions to the user. We want to avoid that, so we have # defaults here for most of the required parameters we know about, in case @@ -202,10 +195,7 @@ sub install_module { print install_string('install_module', { module => $module_name, version => $version }) . "\n"; - if (_always_test($name)) { - CPAN::Shell->install($name); - } - elsif ($test) { + if ($test) { CPAN::Shell->force('install', $name); } else { @@ -220,11 +210,6 @@ sub install_module { $CPAN::Config->{makepl_arg} = $original_makepl; } -sub _always_test { - my ($name) = @_; - return grep(lc($_) eq lc($name), ALWAYS_TEST) ? 1 : 0; -} - sub set_cpan_config { my $do_global = shift; my $bzlib = BZ_LIB; diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm index c22c2de34..8825eb3a7 100644 --- a/Bugzilla/Install/Requirements.pm +++ b/Bugzilla/Install/Requirements.pm @@ -160,13 +160,27 @@ sub REQUIRED_MODULES { version => 0.22, }, { - package => 'Math-Random-Secure', - module => 'Math::Random::Secure', - # This is the first version that installs properly on Windows. - version => '0.05', + package => 'Math-Random-ISAAC', + module => 'Math::Random::ISAAC', + version => '1.0.1', }, ); + if (ON_WINDOWS) { + push(@modules, { + package => 'Win32', + module => 'Win32', + # 0.35 fixes a memory leak in GetOSVersion, which we use. + version => 0.35, + }, + { + package => 'Win32-API', + module => 'Win32::API', + # 0.55 fixes a bug with char* that might affect Bugzilla::RNG. + version => '0.55', + }); + } + my $extra_modules = _get_extension_requirements('REQUIRED_MODULES'); push(@modules, @$extra_modules); return \@modules; @@ -351,16 +365,6 @@ sub OPTIONAL_MODULES { }, ); - if (ON_WINDOWS) { - # SizeLimit needs Win32::API to work on Windows. - push(@modules, { - package => 'Win32-API', - module => 'Win32::API', - version => 0, - feature => ['mod_perl'], - }); - } - my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES'); push(@modules, @$extra_modules); return \@modules; diff --git a/Bugzilla/RNG.pm b/Bugzilla/RNG.pm new file mode 100644 index 000000000..caa63bae2 --- /dev/null +++ b/Bugzilla/RNG.pm @@ -0,0 +1,233 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is Google Inc. +# Portions created by the Initial Developer are Copyright (C) 2011 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Max Kanat-Alexander <mkanat@bugzilla.org> + +package Bugzilla::RNG; +use strict; +use base qw(Exporter); +use Bugzilla::Constants qw(ON_WINDOWS); + +use IO::File; +use Math::Random::ISAAC; +use if ON_WINDOWS, 'Win32::API'; + +our $RNG; +our @EXPORT_OK = qw(rand srand irand); + +# ISAAC, a 32-bit generator, should only be capable of generating numbers +# between 0 and 2^32 - 1. We want _to_float to generate numbers possibly +# including 0, but always less than 1.0. Dividing the integer produced +# by irand() by this number should do that exactly. +use constant DIVIDE_BY => 2**32; + +# How many bytes of seed to read. +use constant SEED_SIZE => 16; # 128 bits. + +################# +# Windows Stuff # +################# + +# The type of cryptographic service provider we want to use. +# This doesn't really matter for our purposes, so we just pick +# PROV_RSA_FULL, which seems reasonable. For more info, see +# http://msdn.microsoft.com/en-us/library/aa380244(v=VS.85).aspx +use constant PROV_RSA_FULL => 1; + +# Flags for CryptGenRandom: +# Don't ever display a UI to the user, just fail if one would be needed. +use constant CRYPT_SILENT => 64; +# Don't require existing public/private keypairs. +use constant CRYPT_VERIFYCONTEXT => 0xF0000000; + +# For some reason, BOOLEAN doesn't work properly as a return type with +# Win32::API. +use constant RTLGENRANDOM_PROTO => <<END; +INT SystemFunction036( + PVOID RandomBuffer, + ULONG RandomBufferLength +) +END + +################# +# RNG Functions # +################# + +sub rand (;$) { + my ($limit) = @_; + my $int = irand(); + return _to_float($int, $limit); +} + +sub irand (;$) { + my ($limit) = @_; + Bugzilla::RNG::srand() if !defined $RNG; + my $int = $RNG->irand(); + if (defined $limit) { + # We can't just use the mod operator because it will bias + # our output. Search for "modulo bias" on the Internet for + # details. This is slower than mod(), but does not have a bias, + # as demonstrated by Math::Random::Secure's uniform.t test. + return int(_to_float($int, $limit)); + } + return $int; +} + +sub srand (;$) { + my ($value) = @_; + # Remove any RNG that might already have been made. + $RNG = undef; + my %args; + if (defined $value) { + $args{seed} = $value; + } + $RNG = _create_rng(\%args); +} + +sub _to_float { + my ($integer, $limit) = @_; + $limit ||= 1; + return ($integer / DIVIDE_BY) * $limit; +} + +########################## +# Seed and PRNG Creation # +########################## + +sub _create_rng { + my ($params) = @_; + + if (!defined $params->{seed}) { + $params->{seed} = _get_seed(); + } + + _check_seed($params->{seed}); + + my @seed_ints = unpack('L*', $params->{seed}); + + my $rng = Math::Random::ISAAC->new(@seed_ints); + + # It's faster to skip the frontend interface of Math::Random::ISAAC + # and just use the backend directly. However, in case the internal + # code of Math::Random::ISAAC changes at some point, we do make sure + # that the {backend} element actually exists first. + return $rng->{backend} ? $rng->{backend} : $rng; +} + +sub _check_seed { + my ($seed) = @_; + if (length($seed) < 8) { + warn "Your seed is less than 8 bytes (64 bits). It could be" + . " easy to crack"; + } + # If it looks like we were seeded with a 32-bit integer, warn the + # user that they are making a dangerous, easily-crackable mistake. + elsif (length($seed) <= 10 and $seed =~ /^\d+$/) { + warn "RNG seeded with a 32-bit integer, this is easy to crack"; + } +} + +sub _get_seed { + return _windows_seed() if ON_WINDOWS; + + if (-r '/dev/urandom') { + return _read_seed_from('/dev/urandom'); + } + + return _read_seed_from('/dev/random'); +} + +sub _read_seed_from { + my ($from) = @_; + + my $fh = IO::File->new($from, "r") or die "$from: $!"; + my $buffer; + $fh->read($buffer, SEED_SIZE); + if (length($buffer) < SEED_SIZE) { + die "Could not read enough seed bytes from $from, got only " + . length($buffer); + } + $fh->close; + return $buffer; +} + +sub _windows_seed { + my ($major, $minor) = (Win32::GetOSVersion())[1,2]; + if ($major < 5) { + die "Bugzilla does not support versions of Windows before" + . " Windows 2000"; + } + # This means Windows 2000. + if ($major == 5 and $minor == 0) { + return _win2k_seed(); + } + + my $rtlgenrand = Win32::API->new('advapi32', RTLGENRANDOM_PROTO); + if (!defined $rtlgenrand) { + die "Could not import RtlGenRand: $^E"; + } + my $buffer = chr(0) x SEED_SIZE; + my $result = $rtlgenrand->Call($buffer, SEED_SIZE); + if (!$result) { + die "RtlGenRand failed: $^E"; + } + return $buffer; +} + +sub _win2k_seed { + my $crypt_acquire = Win32::API->new( + "advapi32", 'CryptAcquireContext', 'PPPNN', 'I'); + if (!defined $crypt_acquire) { + die "Could not import CryptAcquireContext: $^E"; + } + + my $crypt_release = Win32::API->new( + "advapi32", 'CryptReleaseContext', 'NN', 'I'); + if (!defined $crypt_release) { + die "Could not import CryptReleaseContext: $^E"; + } + + my $crypt_gen_random = Win32::API->new( + "advapi32", 'CryptGenRandom', 'NNP', 'I'); + if (!defined $crypt_gen_random) { + die "Could not import CryptGenRandom: $^E"; + } + + my $context = chr(0) x Win32::API::Type->sizeof('PULONG'); + my $acquire_result = $crypt_acquire->Call( + $context, 0, 0, PROV_RSA_FULL, CRYPT_SILENT | CRYPT_VERIFYCONTEXT); + if (!defined $acquire_result) { + die "CryptAcquireContext failed: $^E"; + } + + my $pack_type = Win32::API::Type::packing('PULONG'); + $context = unpack($pack_type, $context); + + my $buffer = chr(0) x SEED_SIZE; + my $rand_result = $crypt_gen_random->Call($context, SEED_SIZE, $buffer); + my $rand_error = $^E; + # We don't check this if it fails, we don't care. + $crypt_release->Call($context, 0); + if (!defined $rand_result) { + die "CryptGenRandom failed: $rand_error"; + } + return $buffer; +} + +1; diff --git a/Bugzilla/Util.pm b/Bugzilla/Util.pm index 4afdef1b4..ac6848bfa 100644 --- a/Bugzilla/Util.pm +++ b/Bugzilla/Util.pm @@ -47,6 +47,7 @@ use base qw(Exporter); detect_encoding); use Bugzilla::Constants; +use Bugzilla::RNG qw(irand); use Date::Parse; use Date::Format; @@ -55,7 +56,6 @@ use DateTime::TimeZone; use Digest; use Email::Address; use List::Util qw(first); -use Math::Random::Secure qw(irand); use Scalar::Util qw(tainted blessed); use Template::Filters; use Text::Wrap; diff --git a/mod_perl.pl b/mod_perl.pl index 460e6216b..2f4016952 100644 --- a/mod_perl.pl +++ b/mod_perl.pl @@ -39,7 +39,6 @@ use Apache2::Log (); use Apache2::ServerUtil; use ModPerl::RegistryLoader (); use File::Basename (); -use Math::Random::Secure; # This loads most of our modules. use Bugzilla (); @@ -49,6 +48,7 @@ use Bugzilla::CGI (); use Bugzilla::Extension (); use Bugzilla::Install::Requirements (); use Bugzilla::Util (); +use Bugzilla::RNG (); # Make warnings go to the virtual host's log and not the main # server log. @@ -68,11 +68,11 @@ my $cgi_path = Bugzilla::Constants::bz_locations()->{'cgi_path'}; my $server = Apache2::ServerUtil->server; my $conf = <<EOT; # Make sure each httpd child receives a different random seed (bug 476622). -# Math::Random::Secure has one srand that needs to be called for +# Bugzilla::RNG has one srand that needs to be called for # every process, and Perl has another. (Various Perl modules still use -# the built-in rand(), even though we only use Math::Random::Secure in -# Bugzilla itself, so we need to srand() both of them.) -PerlChildInitHandler "sub { Math::Random::Secure::srand(); srand(); }" +# the built-in rand(), even though we never use it in Bugzilla itself, +# so we need to srand() both of them.) +PerlChildInitHandler "sub { Bugzilla::RNG::srand(); srand(); }" <Directory "$cgi_path"> AddHandler perl-script .cgi # No need to PerlModule these because they're already defined in mod_perl.pl |