Bugzilla – Attachment 38511 Details for
Bug 86764
VUL-0: CVE-2005-1266: spamassassin DoS
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
IDP Log In
|
Forgot Password
[patch]
3.0.x.patch
3.0.x.patch (text/plain), 20.13 KB, created by
Thomas Biege
on 2005-06-02 09:01:15 UTC
(
hide
)
Description:
3.0.x.patch
Filename:
MIME Type:
Creator:
Thomas Biege
Created:
2005-06-02 09:01:15 UTC
Size:
20.13 KB
patch
obsolete
>Index: lib/Mail/SpamAssassin/Message/Node.pm >=================================================================== >--- lib/Mail/SpamAssassin/Message/Node.pm (revision 171015) >+++ lib/Mail/SpamAssassin/Message/Node.pm (working copy) >@@ -1,5 +1,3 @@ >-# $Id: MIME.pm,v 1.8 2003/10/02 22:59:00 quinlan Exp $ >- > # <@LICENSE> > # Copyright 2004 Apache Software Foundation > # >@@ -61,6 +59,12 @@ > header_order => [] > }; > >+ # deal with any parameters >+ my($opts) = @_; >+ if (defined $opts->{'subparse'}) { >+ $self->{subparse} = $opts->{'subparse'}; >+ } >+ > bless($self,$class); > $self; > } >@@ -501,10 +505,15 @@ > } > elsif ( $cte eq 'Q' ) { > # quoted printable >+ >+ # the RFC states that in the encoded text, "_" is equal to "=20" >+ $data =~ s/_/=20/g; >+ > return Mail::SpamAssassin::Util::qp_decode($data); > } > else { >- die "Unknown encoding type '$cte' in RFC2047 header"; >+ # not possible since the input has already been limited to 'B' and 'Q' >+ die "message: unknown encoding type '$cte' in RFC2047 header"; > } > } > >@@ -521,8 +530,13 @@ > > return $header unless $header =~ /=\?/; > >+ # multiple encoded sections must ignore the interim whitespace. >+ # to avoid possible FPs with (\s+(?==\?))?, look for the whole RE >+ # separated by whitespace. >+ 1 while ($header =~ s/(=\?[\w_-]+\?[bqBQ]\?[^?]+\?=)\s+(=\?[\w_-]+\?[bqBQ]\?[^?]+\?=)/$1$2/g); >+ > $header =~ >- s/=\?([\w_-]+)\?([bqBQ])\?(.*?)\?=/__decode_header($1, uc($2), $3)/ge; >+ s/=\?([\w_-]+)\?([bqBQ])\?([^?]+)\?=/__decode_header($1, uc($2), $3)/ge; > > return $header; > } >Index: lib/Mail/SpamAssassin/Message.pm >=================================================================== >--- lib/Mail/SpamAssassin/Message.pm (revision 171048) >+++ lib/Mail/SpamAssassin/Message.pm (working copy) >@@ -75,6 +75,9 @@ > C<spamassassin -d>, which only needs the pristine header and body which > is always handled when the object is created. > >+C<subparse> specifies how many levels of message/* attachment should be parsed >+into a subtree. Defaults to 1. >+ > =cut > > # month mappings (ripped from Util.pm) >@@ -103,6 +106,11 @@ > my $message = $opts->{'message'} || \*STDIN; > my $parsenow = $opts->{'parsenow'} || 0; > >+ # Specifies whether or not to parse message/rfc822 parts into its own tree. >+ # If the # > 0, it'll subparse, otherwise it won't. By default, do one >+ # level deep. >+ $self->{subparse} = defined $opts->{'subparse'} ? $opts->{'subparse'} : 1; >+ > # protect it from abuse ... > local $_; > >@@ -120,110 +128,110 @@ > @message = split ( /^/m, $message ); > } > >+ return $self unless @message; >+ >+ # Pull off mbox and mbx separators >+ if ( $message[0] =~ /^From\s/ ) { >+ # mbox formated mailbox >+ $self->{'mbox_sep'} = shift @message; >+ } elsif ($message[0] =~ MBX_SEPARATOR) { >+ $_ = shift @message; >+ >+ # Munge the mbx message separator into mbox format as a sort of >+ # de facto portability standard in SA's internals. We need to >+ # to this so that Mail::SpamAssassin::Util::parse_rfc822_date >+ # can parse the date string... >+ if (/([\s|\d]\d)-([a-zA-Z]{3})-(\d{4})\s(\d{2}):(\d{2}):(\d{2})/) { >+ # $1 = day of month >+ # $2 = month (text) >+ # $3 = year >+ # $4 = hour >+ # $5 = min >+ # $6 = sec >+ my @arr = localtime(timelocal($6,$5,$4,$1,$MONTH{lc($2)}-1,$3)); >+ my $address; >+ foreach (@message) { >+ if (/From:\s[^<]+<([^>]+)>/) { >+ $address = $1; >+ last; >+ } elsif (/From:\s([^<^>]+)/) { >+ $address = $1; >+ last; >+ } >+ } >+ $self->{'mbox_sep'} = "From $address $DAY_OF_WEEK[$arr[6]] $2 $1 $4:$5:$6 $3\n"; >+ } >+ } >+ > # Go through all the headers of the message > my $header = ''; >- my $boundary; >- while ( my $last = shift @message ) { >- if ( $last =~ /^From\s/ ) { >- # mbox formated mailbox >- $self->{'mbox_sep'} = $last; >- next; >- } elsif ($last =~ MBX_SEPARATOR) { >- # Munge the mbx message separator into mbox format as a sort of >- # de facto portability standard in SA's internals. We need to >- # to this so that Mail::SpamAssassin::Util::parse_rfc822_date >- # can parse the date string... >- if (/([\s|\d]\d)-([a-zA-Z]{3})-(\d{4})\s(\d{2}):(\d{2}):(\d{2})/o) { >- # $1 = day of month >- # $2 = month (text) >- # $3 = year >- # $4 = hour >- # $5 = min >- # $6 = sec >- my @arr = localtime(timelocal($6,$5,$4,$1,$MONTH{lc($2)}-1,$3)); >- my $address; >- foreach (@message) { >- if (/From:\s[^<]+<([^>]+)>/) { >- $address = $1; >- last; >- } elsif (/From:\s([^<^>]+)/) { >- $address = $1; >- last; >- } >- } >- $self->{'mbox_sep'} = "From $address $DAY_OF_WEEK[$arr[6]] $2 $1 $4:$5:$6 $3\n"; >- next; >- } >+ while ( my $current = shift @message ) { >+ unless ($self->{'missing_head_body_separator'}) { >+ $self->{'pristine_headers'} .= $current; > } > >- # Store the non-modified headers in a scalar >- $self->{'pristine_headers'} .= $last; >+ # NB: Really need to figure out special folding rules here! >+ if ( $current =~ /^[ \t]/ ) { >+ # This wasn't useful in terms of a rule, but we may want to treat it >+ # specially at some point. Perhaps ignore it? >+ #unless ($current =~ /\S/) { >+ # $self->{'obsolete_folding_whitespace'} = 1; >+ #} > >- # NB: Really need to figure out special folding rules here! >- if ( $last =~ /^[ \t]+/ ) { # if its a continuation >+ # append continuations if there's a header in process > if ($header) { >- $header .= $last; # fold continuations >+ $header .= $current; >+ } >+ } >+ else { >+ # Ok, there's a header here, let's go ahead and add it in. >+ if ($header) { >+ # Yes, the /s is needed to match \n too. >+ my ($key, $value) = split (/:\s*(?=.)/s, $header, 2); > >- # If we're currently dealing with a content-type header, and there's a >- # boundary defined, use it. Since there could be multiple >- # content-type headers in a message, the last one will be the one we >- # should use, so just keep updating as they come in. >- if ($header =~ /^content-type:\s*(\S.*)$/is) { >- my($type,$temp_boundary) = Mail::SpamAssassin::Util::parse_content_type($1); >- $boundary = $temp_boundary if ($type =~ /^multipart/ && defined $temp_boundary); >- } >+ # If it's not a valid header (aka: not in the form "foo: bar"), skip it. >+ if (defined $value) { >+ # limit the length of the pairs we store >+ if (length($key) > MAX_HEADER_KEY_LENGTH) { >+ $key = substr($key, 0, MAX_HEADER_KEY_LENGTH); >+ $self->{'truncated_header'} = 1; >+ } >+ if (length($value) > MAX_HEADER_VALUE_LENGTH) { >+ $value = substr($value, 0, MAX_HEADER_VALUE_LENGTH); >+ $self->{'truncated_header'} = 1; >+ } >+ $self->header($key, $value); >+ } >+ } > >- # Go onto the next header line, unless the next line is a >- # multipart mime boundary, where we know we're going to stop >- # below, so drop through for final header processing. >- next unless (defined $boundary && @message && $message[0] =~ /^--\Q$boundary\E(?:--|\s*$)/); >- } >- else { >- # There was no previous header and this is just "out there"? >- # Ignore it! >- next; >- } >+ # not a continuation... >+ $header = $current; > } > >- # Ok, there's a header here, let's go ahead and add it in. > if ($header) { >- # Yes, the /s is needed to match \n too. >- my ($key, $value) = split (/:\s*(?=.)/s, $header, 2); >- >- # If it's not a valid header (aka: not in the form "foo: bar"), skip it. >- if (defined $value) { >- # limit the length of the pairs we store >- if (length($key) > MAX_HEADER_KEY_LENGTH) { >- $key = substr($key, 0, MAX_HEADER_KEY_LENGTH); >- $self->{'truncated_header'} = 1; >- } >- if (length($value) > MAX_HEADER_VALUE_LENGTH) { >- $value = substr($value, 0, MAX_HEADER_VALUE_LENGTH); >- $self->{'truncated_header'} = 1; >- } >- $self->header($key, $value); >- >- # If we're currently dealing with a content-type header, and there's a >- # boundary defined, use it. Since there could be multiple >- # content-type headers in a message, the last one will be the one we >- # should use, so just keep updating as they come in. >- if (lc $key eq 'content-type') { >- my($type,$temp_boundary) = Mail::SpamAssassin::Util::parse_content_type($value); >- $boundary = $temp_boundary if ($type =~ /^multipart/ && defined $temp_boundary); >- } >+ if ($header =~ /^\r?$/) { >+ last; > } >+ else { >+ # Check for missing head/body separator >+ # RFC 2822, s2.2: >+ # A field name MUST be composed of printable US-ASCII characters >+ # (i.e., characters that have values between 33 (041) and 126 (176), inclusive), >+ # except colon (072). >+ # FOR THIS NEXT PART: list off the valid REs for what can be next: >+ # Header, header continuation, blank line >+ if (!@message || $message[0] !~ /^(?:[\041-\071\073-\176]+:|[ \t]|\r?$)/ || $message[0] =~ /^--/) { >+ # No body or no separator before mime boundary is invalid >+ $self->{'missing_head_body_separator'} = 1; >+ >+ # we *have* to go back through again to make sure we catch the last >+ # header, so fake a separator and loop again. >+ unshift(@message, "\n"); >+ } >+ } > } >- >- # not a continuation... >- $header = $last; >- >- # Ok, we found the header/body blank line ... >- last if ($last =~ /^\r?$/m); >- >- # Alternately, if a multipart mime boundary is found in the header area, >- # aka it's malformed, exit out as well and treat it as part of the body. >- last if (defined $boundary && @message && $message[0] =~ /^--\Q$boundary\E(?:--|\s*$)/); > } >+ undef $header; > > # Store the pristine body for later -- store as a copy since @message > # will get modified below >@@ -484,7 +492,8 @@ > # Else, there's no boundary, so leave the whole part... > } > >- my $part_msg = Mail::SpamAssassin::Message::Node->new(); # prepare a new tree node >+ # prepare a new tree node >+ my $part_msg = Mail::SpamAssassin::Message::Node->new({ subparse=>$msg->{subparse} }); > my $in_body = 0; > my $header; > my $part_array; >@@ -531,46 +540,56 @@ > > # make sure we start with a new clean node > $in_body = 0; >- $part_msg = Mail::SpamAssassin::Message::Node->new(); >+ $part_msg = Mail::SpamAssassin::Message::Node->new({ subparse=>$msg->{subparse} }); > undef $part_array; > undef $header; > > next; > } > >- if ($in_body) { >- # we run into a perl bug if the lines are astronomically long (probably >- # due to lots of regexp backtracking); so cut short any individual line >- # over MAX_BODY_LINE_LENGTH bytes in length. This can wreck HTML >- # totally -- but IMHO the only reason a luser would use >- # MAX_BODY_LINE_LENGTH-byte lines is to crash filters, anyway. >- while (length ($_) > MAX_BODY_LINE_LENGTH) { >- push (@{$part_array}, substr($_, 0, MAX_BODY_LINE_LENGTH)."\n"); >- substr($_, 0, MAX_BODY_LINE_LENGTH) = ''; >- } >- push ( @{$part_array}, $_ ); >- } >- else { >+ if (!$in_body) { > s/\s+$//; >- if (m/^\S/) { >+ if (m/^[\041-\071\073-\176]+:/) { > if ($header) { > my ( $key, $value ) = split ( /:\s*/, $header, 2 ); > $part_msg->header( $key, $value ); > } > $header = $_; >+ next; > } >- elsif (/^$/) { >+ elsif (/^[ \t]/) { >+ $_ =~ s/^\s*//; >+ $header .= $_; >+ next; >+ } >+ else { > if ($header) { > my ( $key, $value ) = split ( /:\s*/, $header, 2 ); > $part_msg->header( $key, $value ); > } > $in_body = 1; >+ >+ # if there's a blank line separator, that's good. if there isn't, >+ # it's a body line, so drop through. >+ if (/^\r?$/) { >+ next; >+ } >+ else { >+ $self->{'missing_mime_head_body_separator'} = 1; >+ } > } >- else { >- $_ =~ s/^\s*//; >- $header .= $_; >- } > } >+ >+ # we run into a perl bug if the lines are astronomically long (probably >+ # due to lots of regexp backtracking); so cut short any individual line >+ # over MAX_BODY_LINE_LENGTH bytes in length. This can wreck HTML >+ # totally -- but IMHO the only reason a luser would use >+ # MAX_BODY_LINE_LENGTH-byte lines is to crash filters, anyway. >+ while (length ($_) > MAX_BODY_LINE_LENGTH) { >+ push (@{$part_array}, substr($_, 0, MAX_BODY_LINE_LENGTH)."\n"); >+ substr($_, 0, MAX_BODY_LINE_LENGTH) = ''; >+ } >+ push ( @{$part_array}, $_ ); > } > > } >@@ -604,12 +623,16 @@ > > # If this part is a message/* part, and the parent isn't also a > # message/* part (ie: the main part) go ahead and parse into a tree. >- if ($part_msg->{'type'} =~ /^message\b/i) { >+ if ($part_msg->{'type'} =~ /^message\b/i && ($msg->{subparse} > 0)) { > # Get the part ready... > my $message = $part_msg->decode(); > > if ($message) { >- my $msg_obj = Mail::SpamAssassin::Message->new({message=>$message, parsenow=>1}); >+ my $msg_obj = Mail::SpamAssassin::Message->new({ >+ message => $message, >+ parsenow => 1, >+ subparse => $msg->{subparse}-1, >+ }); > > # main message is a message/* part ... > if ($msg == $part_msg) { >@@ -627,6 +650,10 @@ > return; > } > } >+ else { >+ # leaves don't need the subparse value, so get rid of it >+ delete $part_msg->{subparse}; >+ } > > # Add the new part as a child to the parent > # NOTE: if the message only has this one part, we'll be recursive so delete >@@ -656,6 +683,10 @@ > my @parts = $self->find_parts(qr/^(?:text|message)\b/i,1); > return $self->{text_rendered} unless @parts; > >+ # the html metadata may have already been set, so let's not bother if it's >+ # already been done. >+ my $html_needs_setting = !exists $self->{metadata}->{html}; >+ > # Go through each part > my $text = $self->get_header ('subject') || ''; > for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) { >@@ -670,8 +701,12 @@ > $text .= $rnd; > > # TVD - if there are multiple parts, what should we do? >- # right now, just use the last one ... >- $self->{metadata}->{html} = $p->{html_results} if ( $type eq 'text/html' ); >+ # right now, just use the last one. we may need to give some priority >+ # at some point, ie: use text/html rendered if it exists, or >+ # text/plain rendered as html otherwise. >+ if ($html_needs_setting && $type eq 'text/html') { >+ $self->{metadata}->{html} = $p->{html_results}; >+ } > } > else { > $text .= $p->decode(); >@@ -708,6 +743,10 @@ > my @parts = $self->find_parts(qr/^(?:text|message)\b/i,1); > return $self->{text_visible_rendered} unless @parts; > >+ # the html metadata may have already been set, so let's not bother if it's >+ # already been done. >+ my $html_needs_setting = !exists $self->{metadata}->{html}; >+ > # Go through each part > my $text = $self->get_header ('subject') || ''; > for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) { >@@ -720,6 +759,14 @@ > if ( defined $rnd ) { > # Only text/* types are rendered ... > $text .= $rnd; >+ >+ # TVD - if there are multiple parts, what should we do? >+ # right now, just use the last one. we may need to give some priority >+ # at some point, ie: use text/html rendered if it exists, or >+ # text/plain rendered as html otherwise. >+ if ($html_needs_setting && $type eq 'text/html') { >+ $self->{metadata}->{html} = $p->{html_results}; >+ } > } > else { > $text .= $p->decode(); >@@ -727,9 +774,9 @@ > } > > # whitespace handling (warning: small changes have large effects!) >- $text =~ s/\n+\s*\n+/\f/gs; # double newlines => form feed >- $text =~ tr/ \t\n\r\x0b\xa0/ /s; # whitespace => space >- $text =~ tr/\f/\n/; # form feeds => newline >+ $text =~ s/\n+\s*\n+/\f/gs; # double newlines => form feed >+ $text =~ tr/ \t\n\r\x0b\xa0/ /s; # whitespace => space >+ $text =~ tr/\f/\n/; # form feeds => newline > > my @textary = split_into_array_of_short_lines ($text); > $self->{text_visible_rendered} = \@textary; >@@ -750,6 +797,10 @@ > my @parts = $self->find_parts(qr/^(?:text|message)\b/i,1); > return $self->{text_invisible_rendered} unless @parts; > >+ # the html metadata may have already been set, so let's not bother if it's >+ # already been done. >+ my $html_needs_setting = !exists $self->{metadata}->{html}; >+ > # Go through each part > my $text = ''; > for(my $pt = 0 ; $pt <= $#parts ; $pt++ ) { >@@ -762,13 +813,21 @@ > if ( defined $rnd ) { > # Only text/* types are rendered ... > $text .= $rnd; >+ >+ # TVD - if there are multiple parts, what should we do? >+ # right now, just use the last one. we may need to give some priority >+ # at some point, ie: use text/html rendered if it exists, or >+ # text/plain rendered as html otherwise. >+ if ($html_needs_setting && $type eq 'text/html') { >+ $self->{metadata}->{html} = $p->{html_results}; >+ } > } > } > > # whitespace handling (warning: small changes have large effects!) >- $text =~ s/\n+\s*\n+/\f/gs; # double newlines => form feed >- $text =~ tr/ \t\n\r\x0b\xa0/ /s; # whitespace => space >- $text =~ tr/\f/\n/; # form feeds => newline >+ $text =~ s/\n+\s*\n+/\f/gs; # double newlines => form feed >+ $text =~ tr/ \t\n\r\x0b\xa0/ /s; # whitespace => space >+ $text =~ tr/\f/\n/; # form feeds => newline > > my @textary = split_into_array_of_short_lines ($text); > $self->{text_invisible_rendered} = \@textary; >Index: t/mimeparse.t >=================================================================== >--- t/mimeparse.t (revision 171015) >+++ t/mimeparse.t (working copy) >@@ -55,7 +55,7 @@ > ], > > "$prefix/t/data/nice/mime8" => [ >- join("\n",'multipart/mixed','application/postscript','binary','message/rfc822,multipart/mixed,text/plain,multipart/parallel,image/gif,audio/basic,application/atomicmail,message/rfc822,audio/x-sun'), >+ join("\n",'multipart/mixed','application/postscript','binary','message/rfc822,multipart/mixed,text/plain,multipart/parallel,image/gif,audio/basic,application/atomicmail,message/rfc822'), > '07fdde1c24f216b05813f6a1ae0c7c1c0f84c42b', > '03e5acb518e8aca0b3a7b18f2d94b5efe73495b2' > ], >@@ -79,15 +79,22 @@ > ], > > "$prefix/t/data/nice/mime9" => [ >- join("\n",'multipart/mixed','text/plain','message/rfc822,message/rfc822,multipart/mixed,multipart/alternative,text/plain,text/html,image/jpeg'), >+ join("\n",'multipart/mixed','text/plain','message/rfc822,message/rfc822'), > '5cdcabdb89c5fbb3a5e0c0473599668927045d9c', >- 'f80584aff917e03d54663422918b58e4689cf993', >- '0228600472b0820b3b326d9d7842eef3af811cb2', >- '0b9fb462ad496d926ef65db0da8da451d7815ab6', > ], > ); > >-my $numtests = 0; >+# initialize SpamAssassin >+my $sa = Mail::SpamAssassin->new({ >+ rules_filename => "$prefix/t/log/test_rules_copy", >+ site_rules_filename => "$prefix/t/log/test_default.cf", >+ userprefs_filename => "$prefix/masses/spamassassin/user_prefs", >+ local_tests_only => 1, >+ debug => 0, >+ dont_copy_prefs => 1, >+}); >+ >+my $numtests = 5; > while ( my($k,$v) = each %files ) { > $numtests += @{$v}; > } >@@ -96,7 +103,7 @@ > > foreach my $k ( sort keys %files ) { > open(INP, $k) || die "Can't find $k:$!"; >- my $mail = Mail::SpamAssassin->parse(\*INP, 1); >+ my $mail = $sa->parse(\*INP, 1); > close(INP); > > my $res = join("\n",$mail->content_summary()); >@@ -124,4 +131,39 @@ > shift @parts; > } > } >+ $mail->finish(); > } >+ >+my @msg; >+my $subject; >+my $mail; >+ >+@msg = ("Subject: =?ISO-8859-1?Q?a?=\n", "\n"); >+$mail = $sa->parse(\@msg); >+$subject = $mail->get_header("Subject"); >+$mail->finish(); >+ok($subject eq "a\n"); >+ >+@msg = ("Subject: =?ISO-8859-1?Q?a?= b\n", "\n"); >+$mail = $sa->parse(\@msg); >+$subject = $mail->get_header("Subject"); >+$mail->finish(); >+ok($subject eq "a b\n"); >+ >+@msg = ("Subject: =?ISO-8859-1?Q?a?= \t =?ISO-8859-1?Q?b?=\n", "\n"); >+$mail = $sa->parse(\@msg); >+$subject = $mail->get_header("Subject"); >+$mail->finish(); >+ok($subject eq "ab\n"); >+ >+@msg = ("Subject: =?ISO-8859-1?Q?a?=\n", " =?ISO-8859-1?Q?_b?=\n", "\n"); >+$mail = $sa->parse(\@msg); >+$subject = $mail->get_header("Subject"); >+$mail->finish(); >+ok($subject eq "a b\n"); >+ >+@msg = ("Subject: =?ISO-8859-1?Q?a?=\n", " =?ISO-8859-1?Q?_b?= mem_brain =? invalid ?=\n", "\n"); >+$mail = $sa->parse(\@msg); >+$subject = $mail->get_header("Subject"); >+$mail->finish(); >+ok($subject eq "a b mem_brain =? invalid ?=\n"); >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
Actions:
View
|
Diff
Attachments on
bug 86764
: 38511 |
38512
|
38663