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