Skip to content

Commit 1f545fc

Browse files
committed
updated to 5.0.4
1 parent 4cc8e3d commit 1f545fc

26 files changed

+280
-131
lines changed

Bugzilla/CGI.pm

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,69 @@ sub close_standby_message {
288288
}
289289
}
290290

291+
our $ALLOW_UNSAFE_RESPONSE = 0;
292+
# responding to text/plain or text/html is safe
293+
# responding to any request with a referer header is safe
294+
# some things need to have unsafe responses (attachment.cgi)
295+
# everything else should get a 403.
296+
sub _prevent_unsafe_response {
297+
my ($self, $headers) = @_;
298+
my $safe_content_type_re = qr{
299+
^ (*COMMIT) # COMMIT makes the regex faster
300+
# by preventing back-tracking. see also perldoc pelre.
301+
# application/x-javascript, xml, atom+xml, rdf+xml, xml-dtd, and json
302+
(?: application/ (?: x(?: -javascript | ml (?: -dtd )? )
303+
| (?: atom | rdf) \+ xml
304+
| json )
305+
# text/csv, text/calendar, text/plain, and text/html
306+
| text/ (?: c (?: alendar | sv )
307+
| plain
308+
| html )
309+
# used for HTTP push responses
310+
| multipart/x-mixed-replace)
311+
}sx;
312+
my $safe_referer_re = do {
313+
# Note that urlbase must end with a /.
314+
# It almost certainly does, but let's be extra careful.
315+
my $urlbase = correct_urlbase();
316+
$urlbase =~ s{/$}{};
317+
qr{
318+
# Begins with literal urlbase
319+
^ (*COMMIT)
320+
\Q$urlbase\E
321+
# followed by a slash or end of string
322+
(?: /
323+
| $ )
324+
}sx
325+
};
326+
327+
return if $ALLOW_UNSAFE_RESPONSE;
328+
329+
if (Bugzilla->usage_mode == USAGE_MODE_BROWSER) {
330+
# Safe content types are ones that arn't images.
331+
# For now let's assume plain text and html are not valid images.
332+
my $content_type = $headers->{'-type'} // $headers->{'-content_type'} // 'text/html';
333+
my $is_safe_content_type = $content_type =~ $safe_content_type_re;
334+
335+
# Safe referers are ones that begin with the urlbase.
336+
my $referer = $self->referer;
337+
my $is_safe_referer = $referer && $referer =~ $safe_referer_re;
338+
339+
if (!$is_safe_referer && !$is_safe_content_type) {
340+
print $self->SUPER::header(-type => 'text/html', -status => '403 Forbidden');
341+
if ($content_type ne 'text/html') {
342+
print "Untrusted Referer Header\n";
343+
if ($ENV{MOD_PERL}) {
344+
my $r = $self->r;
345+
$r->rflush;
346+
$r->status(200);
347+
}
348+
}
349+
exit;
350+
}
351+
}
352+
}
353+
291354
# Override header so we can add the cookies in
292355
sub header {
293356
my $self = shift;
@@ -302,6 +365,7 @@ sub header {
302365
else {
303366
%headers = @_;
304367
}
368+
$self->_prevent_unsafe_response(\%headers);
305369

306370
if ($self->{'_content_disp'}) {
307371
$headers{'-content_disposition'} = $self->{'_content_disp'};

Bugzilla/Config.pm

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ use autodie qw(:default);
1616

1717
use Bugzilla::Constants;
1818
use Bugzilla::Hook;
19-
use Bugzilla::Util qw(trick_taint);
19+
use Bugzilla::Util qw(trick_taint read_text write_text);
2020

2121
use JSON::XS;
22-
use File::Slurp;
2322
use File::Temp;
2423
use File::Basename;
2524

@@ -284,7 +283,7 @@ sub write_params {
284283
my $param_file = bz_locations()->{'datadir'} . '/params.json';
285284

286285
my $json_data = JSON::XS->new->canonical->pretty->encode($param_data);
287-
write_file($param_file, { binmode => ':utf8', atomic => 1 }, \$json_data);
286+
write_text($param_file, $json_data);
288287

289288
# It's not common to edit parameters and loading
290289
# Bugzilla::Install::Filesystem is slow.
@@ -301,8 +300,8 @@ sub read_param_file {
301300
my $file = bz_locations()->{'datadir'} . '/params.json';
302301

303302
if (-e $file) {
304-
my $data;
305-
read_file($file, binmode => ':utf8', buf_ref => \$data);
303+
my $data = read_text($file);
304+
trick_taint($data);
306305

307306
# If params.json has been manually edited and e.g. some quotes are
308307
# missing, we don't want JSON::XS to leak the content of the file

Bugzilla/Constants.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ use Memoize;
200200
# CONSTANTS
201201
#
202202
# Bugzilla version
203-
use constant BUGZILLA_VERSION => "5.0.3";
203+
use constant BUGZILLA_VERSION => "5.0.4";
204204

205205
# A base link to the current REST Documentation. We place it here
206206
# as it will need to be updated to whatever the current release is.

Bugzilla/DB/Sqlite.pm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ sub sql_date_format {
219219
my ($self, $date, $format) = @_;
220220
$format = "%Y.%m.%d %H:%M:%S" if !$format;
221221
$format =~ s/\%i/\%M/g;
222+
$format =~ s/\%s/\%S/g;
222223
return "STRFTIME(" . $self->quote($format) . ", $date)";
223224
}
224225

Bugzilla/Install/Filesystem.pm

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ use File::Path;
3131
use File::Basename;
3232
use File::Copy qw(move);
3333
use File::Spec;
34-
use File::Slurp;
3534
use IO::File;
3635
use POSIX ();
3736

@@ -536,7 +535,7 @@ sub update_filesystem {
536535

537536
# Remove old assets htaccess file to force recreation with correct values.
538537
if (-e "$assetsdir/.htaccess") {
539-
if (read_file("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
538+
if (read_text("$assetsdir/.htaccess") =~ /<FilesMatch \\\.css\$>/) {
540539
unlink("$assetsdir/.htaccess");
541540
}
542541
}
@@ -782,31 +781,30 @@ sub _update_old_charts {
782781
# to product IDs.
783782
sub _update_old_mining_filenames {
784783
my ($miningdir) = @_;
784+
my $dbh = Bugzilla->dbh;
785785
my @conversion_errors;
786786

787-
require Bugzilla::Product;
788-
789787
# We use a dummy product instance with ID 0, representing all products
790788
my $product_all = {id => 0, name => '-All-'};
791-
bless($product_all, 'Bugzilla::Product');
792789

793790
print "Updating old charting data file names...";
794-
my @products = Bugzilla::Product->get_all();
791+
my @products = @{ $dbh->selectall_arrayref('SELECT id, name FROM products
792+
ORDER BY name', {Slice=>{}}) };
795793
push(@products, $product_all);
796794
foreach my $product (@products) {
797-
if (-e File::Spec->catfile($miningdir, $product->id)) {
795+
if (-e File::Spec->catfile($miningdir, $product->{id})) {
798796
push(@conversion_errors,
799797
{ product => $product,
800-
message => 'A file named "' . $product->id .
798+
message => 'A file named "' . $product->{id} .
801799
'" already exists.' });
802800
}
803801
}
804802

805803
if (! @conversion_errors) {
806804
# Renaming mining files should work now without a hitch.
807805
foreach my $product (@products) {
808-
if (! rename(File::Spec->catfile($miningdir, $product->name),
809-
File::Spec->catfile($miningdir, $product->id))) {
806+
if (! rename(File::Spec->catfile($miningdir, $product->{name}),
807+
File::Spec->catfile($miningdir, $product->{id}))) {
810808
push(@conversion_errors,
811809
{ product => $product,
812810
message => $! });
@@ -822,7 +820,7 @@ sub _update_old_mining_filenames {
822820
print " FAILED:\n";
823821
foreach my $error (@conversion_errors) {
824822
printf "Cannot rename charting data file for product %d (%s): %s\n",
825-
$error->{product}->id, $error->{product}->name,
823+
$error->{product}->{id}, $error->{product}->{name},
826824
$error->{message};
827825
}
828826
print "You need to empty the \"$miningdir\" directory, then run\n",

Bugzilla/Install/Requirements.pm

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,6 @@ sub REQUIRED_MODULES {
155155
module => 'Math::Random::ISAAC',
156156
version => '1.0.1',
157157
},
158-
{
159-
package => 'File-Slurp',
160-
module => 'File::Slurp',
161-
version => '9999.13',
162-
},
163158
{
164159
package => 'JSON-XS',
165160
module => 'JSON::XS',

Bugzilla/JobQueue.pm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use warnings;
1414
use Bugzilla::Constants;
1515
use Bugzilla::Error;
1616
use Bugzilla::Install::Util qw(install_string);
17+
use Bugzilla::Util qw(read_text);
1718
use File::Basename;
18-
use File::Slurp;
1919
use base qw(TheSchwartz);
2020
use fields qw(_worker_pidfile);
2121

@@ -124,7 +124,7 @@ sub subprocess_worker {
124124
# And poll the PID to detect when the working has finished.
125125
# We do this instead of system() to allow for the INT signal to
126126
# interrup us and trigger kill_worker().
127-
my $pid = read_file($self->{_worker_pidfile}, err_mode => 'quiet');
127+
my $pid = read_text($self->{_worker_pidfile}, err_mode => 'quiet');
128128
if ($pid) {
129129
sleep(3) while(kill(0, $pid));
130130
}
@@ -139,7 +139,7 @@ sub subprocess_worker {
139139
sub kill_worker {
140140
my $self = Bugzilla->job_queue();
141141
if ($self->{_worker_pidfile} && -e $self->{_worker_pidfile}) {
142-
my $worker_pid = read_file($self->{_worker_pidfile});
142+
my $worker_pid = read_text($self->{_worker_pidfile});
143143
if ($worker_pid && kill(0, $worker_pid)) {
144144
$self->debug("Stopping worker process");
145145
system "$0 -f -p '" . $self->{_worker_pidfile} . "' stop";

Bugzilla/Migrate.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ sub parse_date {
403403
}
404404
my $tz;
405405
if ($time[6]) {
406-
$tz = Bugzilla->local_timezone->offset_as_string($time[6]);
406+
$tz = DateTime::TimeZone->offset_as_string($time[6]);
407407
}
408408
else {
409409
$tz = $self->config('timezone');

Bugzilla/Template.pm

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ use Digest::MD5 qw(md5_hex);
3232
use File::Basename qw(basename dirname);
3333
use File::Find;
3434
use File::Path qw(rmtree mkpath);
35-
use File::Slurp;
3635
use File::Spec;
3736
use IO::Dir;
3837
use List::MoreUtils qw(firstidx);
@@ -502,7 +501,7 @@ sub _concatenate_css {
502501
next unless -e "$cgi_path/$files{$source}";
503502
my $file = $skins_path . '/' . md5_hex($source) . '.css';
504503
if (!-e $file) {
505-
my $content = read_file("$cgi_path/$files{$source}");
504+
my $content = read_text("$cgi_path/$files{$source}");
506505

507506
# minify
508507
$content =~ s{/\*.*?\*/}{}sg; # comments
@@ -512,7 +511,7 @@ sub _concatenate_css {
512511
# rewrite urls
513512
$content =~ s{url\(([^\)]+)\)}{_css_url_rewrite($source, $1)}eig;
514513

515-
write_file($file, "/* $files{$source} */\n" . $content . "\n");
514+
write_text($file, "/* $files{$source} */\n" . $content . "\n");
516515
}
517516
push @minified, $file;
518517
}
@@ -522,9 +521,9 @@ sub _concatenate_css {
522521
if (!-e $file) {
523522
my $content = '';
524523
foreach my $source (@minified) {
525-
$content .= read_file($source);
524+
$content .= read_text($source);
526525
}
527-
write_file($file, $content);
526+
write_text($file, $content);
528527
}
529528

530529
$file =~ s/^\Q$cgi_path\E\///o;
@@ -563,7 +562,7 @@ sub _concatenate_js {
563562
next unless -e "$cgi_path/$files{$source}";
564563
my $file = $skins_path . '/' . md5_hex($source) . '.js';
565564
if (!-e $file) {
566-
my $content = read_file("$cgi_path/$files{$source}");
565+
my $content = read_text("$cgi_path/$files{$source}");
567566

568567
# minimal minification
569568
$content =~ s#/\*.*?\*/##sg; # block comments
@@ -572,7 +571,7 @@ sub _concatenate_js {
572571
$content =~ s#\n{2,}#\n#g; # blank lines
573572
$content =~ s#(^\s+|\s+$)##g; # whitespace at the start/end of file
574573

575-
write_file($file, ";/* $files{$source} */\n" . $content . "\n");
574+
write_text($file, ";/* $files{$source} */\n" . $content . "\n");
576575
}
577576
push @minified, $file;
578577
}
@@ -582,9 +581,9 @@ sub _concatenate_js {
582581
if (!-e $file) {
583582
my $content = '';
584583
foreach my $source (@minified) {
585-
$content .= read_file($source);
584+
$content .= read_text($source);
586585
}
587-
write_file($file, $content);
586+
write_text($file, $content);
588587
}
589588

590589
$file =~ s/^\Q$cgi_path\E\///o;

Bugzilla/Util.pm

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use parent qw(Exporter);
2424
validate_email_syntax check_email_syntax clean_text
2525
get_text template_var display_value disable_utf8
2626
detect_encoding email_filter
27-
join_activity_entries);
27+
join_activity_entries read_text write_text);
2828

2929
use Bugzilla::Constants;
3030
use Bugzilla::RNG qw(irand);
@@ -39,6 +39,8 @@ use Scalar::Util qw(tainted blessed);
3939
use Text::Wrap;
4040
use Encode qw(encode decode resolve_alias);
4141
use Encode::Guess;
42+
use File::Basename qw(dirname);
43+
use File::Temp qw(tempfile);
4244

4345
sub trick_taint {
4446
require Carp;
@@ -106,6 +108,29 @@ sub html_quote {
106108
return $var;
107109
}
108110

111+
sub read_text {
112+
my ($filename) = @_;
113+
open my $fh, '<:encoding(utf-8)', $filename;
114+
local $/ = undef;
115+
my $content = <$fh>;
116+
close $fh;
117+
return $content;
118+
}
119+
120+
sub write_text {
121+
my ($filename, $content) = @_;
122+
my ($tmp_fh, $tmp_filename) = tempfile('.tmp.XXXXXXXXXX',
123+
DIR => dirname($filename),
124+
UNLINK => 0,
125+
);
126+
binmode $tmp_fh, ':encoding(utf-8)';
127+
print $tmp_fh $content;
128+
close $tmp_fh;
129+
# File::Temp tries for secure files, but File::Slurp used the umask.
130+
chmod(0666 & ~umask, $tmp_filename);
131+
rename $tmp_filename, $filename;
132+
}
133+
109134
sub html_light_quote {
110135
my ($text) = @_;
111136
# admin/table.html.tmpl calls |FILTER html_light| many times.
@@ -588,7 +613,7 @@ sub datetime_from {
588613
second => defined($time[0]) ? int($time[0]) : undef,
589614
# If a timezone was specified, use it. Otherwise, use the
590615
# local timezone.
591-
time_zone => Bugzilla->local_timezone->offset_as_string($time[6])
616+
time_zone => DateTime::TimeZone->offset_as_string($time[6])
592617
|| Bugzilla->local_timezone,
593618
);
594619

0 commit comments

Comments
 (0)