1package JpegTester; 2# Author: Mark Martinec <Mark.Martinec@ijs.si>, 2004-10; 3# The 2-clause BSD license applies to this package JpegTester. 4use strict; 5use re 'taint'; 6 7use vars qw($buf $buf_l $buf_ofs); 8sub makeTwo($) { # ensure at least two characters in $buf, except near eof 9 my($fh) = @_; 10 $buf_l>=0 or die "jpeg: Panic, program error1, pos=$buf_ofs"; 11 if ($buf_l<2) { 12 my($len) = sysread($fh,$buf,2048,$buf_l); # 2k is about the optimum size 13 defined $len or die "jpeg: Can't read: $!\n"; 14 $buf_l += $len; 15 } 16} 17sub takeN($$) { # swallow n characters 18 my($fh,$n) = @_; my($err) = undef; 19 for ($buf_l>=2||makeTwo($fh); $n>0; ) { 20 if ($buf_l<=0) { $err = "Truncated by $n bytes or more"; last } 21 if ($n >= $buf_l) { $n -= $buf_l; $buf_ofs += $buf_l; $buf = '' } 22 else { $buf = substr($buf,$n); $buf_ofs += $n; $n = 0 } 23 $buf_l = length($buf); $buf_l>=2 || makeTwo($fh); 24 } 25 $err; 26} 27sub takeECS($) { # quickly swallow entropy-coded data segment 28 my($fh) = @_; 29 for ($buf_l>=2||makeTwo($fh); $buf_l>0; ) { 30 if ($buf =~ s/^([^\xff]+)//) { $buf_ofs += length($1) } 31 elsif ($buf =~ s/^(\xff\x00)+//) { $buf_ofs += length($1) } 32 else { last } 33# last unless $buf =~ s/^(?: [^\xff] | \xff \x00 )+//x; # Perl Bus error 34 $buf_l = length($buf); $buf_l>=2 || makeTwo($fh); 35 } 36} 37sub takeFill($) { # swallow fill bytes before marker 38 my($fh) = @_; 39 for (makeTwo($fh); $buf_l>0; $buf_l=length($buf),makeTwo($fh)) { 40 if ($buf =~ s/^ \xff+ (?= \xff )//x) { $buf_ofs += length($1) } 41 else { last } 42 } 43} 44sub takeTail($) { # swallow common junk after EOI 45 my($fh) = @_; 46 for (makeTwo($fh); $buf_l>0; $buf_l=length($buf),makeTwo($fh)) { 47 if ($buf =~ s/^[\x00\xff]+//) { $buf_ofs += length($1) } 48 else { last } 49 } 50} 51 52# exit status: 0:clean; 1:exploit; 2:corrupted 53sub test_jpeg($;@) { 54 my($fn) = @_; # file name to be checked 55 local(*F); my($fh) = \*F; 56 open($fh,"<$fn") or die "jpeg: Can't open file $fn for reading: $!"; 57 binmode($fh) or die "jpeg: Can't set binmode on $fn: $!"; 58 $buf = ''; $buf_l = 0; $buf_ofs = 0; makeTwo($fh); my(@r) = (0,"jpeg ok"); 59 if ($buf !~ /^\xff\xd8/) { @r = (0,"not jpeg") } 60 else { takeN($fh,2); if ($buf !~ /^\xff/) { @r = (0,"not jpeg") } } 61 if ($r[1] eq "jpeg ok") { 62 my($ecs_ok) = 0; local($1); 63 for (;;) { # keep at least 2 chars in buff except near eof 64 if ($buf_l<=0 || $buf eq "\xff") { 65 @r = (2,"Truncated, no EOI, pos=$buf_ofs") if !$r[0]; 66 last; 67 } elsif ($buf =~ /^( [^\xff] | \xff \x00 )/x) { # ecs 68 @r = (2,"Unexpected entropy-coded data segment, pos=$buf_ofs") 69 if !$ecs_ok && !$r[0]; 70 takeECS($fh); $ecs_ok = 0; 71 } elsif ($buf =~ /^ \xff+ (?= \xff ) /x) { # fill bytes before marker 72 takeFill($fh); $ecs_ok = 0; 73 } elsif ($buf =~ /^ \xff ([^\x00\xff]) /x) { # marker 74 my($m) = $1; takeN($fh,2); 75 if ($m =~ /[\xd0-\xd7]/) { # RSTi 76# printf("marker segm, pos=%d, marker=0x%02X\n", $buf_ofs,ord($m)); 77 $ecs_ok = 1; 78 } elsif ($m =~ /[\x01\xd8]/) { # TEM, SOI 79# printf("marker segm, pos=%d, marker=0x%02X\n", $buf_ofs,ord($m)); 80 $ecs_ok = 0; 81 } 82 elsif ($m eq "\xd9") { # EOI 83# printf("marker segm, pos=%d, marker=0x%02X\n", $buf_ofs,ord($m)); 84 takeFill($fh); $ecs_ok = 0; 85 @r = (2,"Trailing garbage, pos=$buf_ofs") if $buf_l>0 && !$r[0]; 86 last; 87 } else { # marker segment 88 $ecs_ok = $m eq "\xda"; # SOS 89 my($len) = unpack("n",substr($buf,0,2)); 90# printf("marker segm len %d, pos=%d, marker=0x%02X\n", $len,$buf_ofs,ord($m)); 91 @r = (1,sprintf("Invalid marker segm len %d, pos=%d, marker=0x%02X", 92 $len,$buf_ofs,ord($m)) ) if $len<2; 93 my($err) = takeN($fh,$len); 94 @r = (2,"$err, pos=$buf_ofs") if defined $err && !$r[0]; 95 } 96 } else { die "jpeg: Panic, program error2, pos=$buf_ofs" } 97 $buf_l>=2 || makeTwo($fh); 98 } 99 } 100 close($fh) or die "jpeg: Can't close $fn: $!"; 101 $r[1] = "bad jpeg: ".$r[1] if $r[0]; 102 @r; 103} 104 1051; # insure a defined return 106