X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Flib%2FRT%2FUtil.pm;h=06cd046bc1c968ee55c541a0bb213b26e7d3ca74;hb=de9d037528895f7151a9aead6724ce2df95f9586;hp=fec3168454a21a3fb87daf265eceee60ed50be4f;hpb=fc6209f398899f0211cfcedeb81a3cd65e04a941;p=freeside.git diff --git a/rt/lib/RT/Util.pm b/rt/lib/RT/Util.pm index fec316845..06cd046bc 100644 --- a/rt/lib/RT/Util.pm +++ b/rt/lib/RT/Util.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -50,8 +50,11 @@ package RT::Util; use strict; use warnings; + use base 'Exporter'; -our @EXPORT = qw/safe_run_child/; +our @EXPORT = qw/safe_run_child mime_recommended_filename/; + +use Encode qw/encode/; sub safe_run_child (&) { my $our_pid = $$; @@ -64,66 +67,155 @@ sub safe_run_child (&) { # values. Instead we set values, eval code, check pid # on failure and reset values only in our original # process - my $dbh = $RT::Handle->dbh; + my ($oldv_dbh, $oldv_rth); + my $dbh = $RT::Handle ? $RT::Handle->dbh : undef; + $oldv_dbh = $dbh->{'InactiveDestroy'} if $dbh; $dbh->{'InactiveDestroy'} = 1 if $dbh; - $RT::Handle->{'DisconnectHandleOnDestroy'} = 0; + $oldv_rth = $RT::Handle->{'DisconnectHandleOnDestroy'} if $RT::Handle; + $RT::Handle->{'DisconnectHandleOnDestroy'} = 0 if $RT::Handle; + + my ($reader, $writer); + pipe( $reader, $writer ); my @res; my $want = wantarray; eval { + my $code = shift; + local @ENV{ 'LANG', 'LC_ALL' } = ( 'C', 'C' ); unless ( defined $want ) { - _safe_run_child( @_ ); + $code->(); } elsif ( $want ) { - @res = _safe_run_child( @_ ); + @res = $code->(); } else { - @res = ( scalar _safe_run_child( @_ ) ); + @res = ( scalar $code->() ); } + exit 0 if $our_pid != $$; 1; } or do { my $err = $@; + $err =~ s/^Stack:.*$//ms; if ( $our_pid == $$ ) { - $RT::Logger->error( $err ); - $dbh->{'InactiveDestroy'} = 0 if $dbh; - $RT::Handle->{'DisconnectHandleOnDestroy'} = 1; + $dbh->{'InactiveDestroy'} = $oldv_dbh if $dbh; + $RT::Handle->{'DisconnectHandleOnDestroy'} = $oldv_rth if $RT::Handle; + die "System Error: $err"; + } else { + print $writer "System Error: $err"; + exit 1; } - $err =~ s/^Stack:.*$//ms; - #TODO we need to localize this - die 'System Error: ' . $err; }; + + close($writer); + $reader->blocking(0); + my ($response) = $reader->getline; + warn $response if $response; + + $dbh->{'InactiveDestroy'} = $oldv_dbh if $dbh; + $RT::Handle->{'DisconnectHandleOnDestroy'} = $oldv_rth if $RT::Handle; return $want? (@res) : $res[0]; } -sub _safe_run_child { - local @ENV{ 'LANG', 'LC_ALL' } = ( 'C', 'C' ); +=head2 mime_recommended_filename( MIME::Head|MIME::Entity ) + +# mimic our own recommended_filename +# since MIME-tools 5.501, head->recommended_filename requires the head are +# mime encoded, we don't meet this yet. + +=cut + +sub mime_recommended_filename { + my $head = shift; + $head = $head->head if $head->isa('MIME::Entity'); + + for my $attr_name (qw( content-disposition.filename content-type.name )) { + my $value = Encode::decode("UTF-8",$head->mime_attr($attr_name)); + if ( defined $value && $value =~ /\S/ ) { + return $value; + } + } + return; +} + +sub assert_bytes { + my $string = shift; + return unless utf8::is_utf8($string); + return unless $string =~ /([^\x00-\x7F])/; + + my $msg; + if (ord($1) > 255) { + $msg = "Expecting a byte string, but was passed characters"; + } else { + $msg = "Expecting a byte string, but was possibly passed charcters;" + ." if the string is actually bytes, please use utf8::downgrade"; + } + $RT::Logger->warn($msg, Carp::longmess()); + +} + + +=head2 C + +Compares two strings for equality in constant-time. Replacement for the C +operator designed to avoid timing side-channel vulnerabilities. Returns zero +or one. + +This is intended for use in cryptographic subsystems for comparing well-formed +data such as hashes - not for direct use with user input or as a general +replacement for the C operator. + +The two string arguments B be of equal length. If the lengths differ, +this function will call C, as proceeding with execution would create +a timing vulnerability. Length is defined by characters, not bytes. + +Strings that should be treated as binary octets rather than Unicode text +should pass a true value for the binary flag. + +This code has been tested to do what it claims. Do not change it without +thorough statistical timing analysis to validate the changes. + +Added to resolve CVE-2017-5361 - return shift->() if $ENV{'MOD_PERL'} || $CGI::SpeedyCGI::i_am_speedy; +For more on timing attacks, see this Wikipedia article: +B - # We need to reopen stdout temporarily, because in FCGI - # environment, stdout is tied to FCGI::Stream, and the child - # of the run3 wouldn't be able to reopen STDOUT properly. - my $stdin = IO::Handle->new; - $stdin->fdopen( 0, 'r' ); - local *STDIN = $stdin; +=cut - my $stdout = IO::Handle->new; - $stdout->fdopen( 1, 'w' ); - local *STDOUT = $stdout; +sub constant_time_eq { + my ($a, $b, $binary) = @_; - my $stderr = IO::Handle->new; - $stderr->fdopen( 2, 'w' ); - local *STDERR = $stderr; + my $result = 0; - return shift->(); + # generic error message avoids potential information leaks + my $generic_error = "Cannot compare values"; + die $generic_error unless defined $a and defined $b; + die $generic_error unless length $a == length $b; + die $generic_error if ref($a) or ref($b); + + for (my $i = 0; $i < length($a); $i++) { + my $a_char = substr($a, $i, 1); + my $b_char = substr($b, $i, 1); + + my (@a_octets, @b_octets); + + if ($binary) { + @a_octets = ord($a_char); + @b_octets = ord($b_char); + } + else { + # encode() is set to die on malformed + @a_octets = unpack("C*", encode('UTF-8', $a_char, Encode::FB_CROAK)); + @b_octets = unpack("C*", encode('UTF-8', $b_char, Encode::FB_CROAK)); + } + + die $generic_error if (scalar @a_octets) != (scalar @b_octets); + + for (my $j = 0; $j < scalar @a_octets; $j++) { + $result |= $a_octets[$j] ^ $b_octets[$j]; + } + } + return 0 + not $result; } -eval "require RT::Util_Vendor"; -if ($@ && $@ !~ qr{^Can't locate RT/Util_Vendor.pm}) { - die $@; -}; -eval "require RT::Util_Local"; -if ($@ && $@ !~ qr{^Can't locate RT/Util_Local.pm}) { - die $@; -}; +RT::Base->_ImportOverlays(); 1;