- creating an image where the size of the allocated image buffer would
authorTony Cook <tony@develop=help.com>
Fri, 12 Nov 2004 05:03:11 +0000 (05:03 +0000)
committerTony Cook <tony@develop=help.com>
Fri, 12 Nov 2004 05:03:11 +0000 (05:03 +0000)
  overflow an integer would cause too small a buffer to be allocated.
  This could potentially be a security hole.
  partly resolves https://rt.cpan.org/Ticket/Display.html?id=8213

Changes
image.c
img16.c
imgdouble.c
palimg.c
t/t01introvert.t
t/t021sixteen.t
t/t022double.t
t/t023palette.t

diff --git a/Changes b/Changes
index 7eb7646..5a356ac 100644 (file)
--- a/Changes
+++ b/Changes
@@ -884,6 +884,10 @@ Revision history for Perl extension Imager.
 - updated download locations for the various libraries that Imager 
   depends on.  Added some advice for cygwin.
 - more information on gif library versions in README and Makefile.PL
+- creating an image where the size of the allocated image buffer would
+  overflow an integer would cause too small a buffer to be allocated.
+  This could potentially be a security hole.
+  partly resolves https://rt.cpan.org/Ticket/Display.html?id=8213
 
 =================================================================
 
diff --git a/image.c b/image.c
index 8dfdb09..eba037c 100644 (file)
--- a/image.c
+++ b/image.c
@@ -348,6 +348,8 @@ Re-new image reference
 
 i_img *
 i_img_empty_ch(i_img *im,int x,int y,int ch) {
+  int bytes;
+
   mm_log((1,"i_img_empty_ch(*im %p, x %d, y %d, ch %d)\n", im, x, y, ch));
 
   if (x < 1 || y < 1) {
@@ -358,6 +360,12 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) {
     i_push_errorf(0, "channels must be between 1 and %d", MAXCHANNELS);
     return NULL;
   }
+  /* check this multiplication doesn't overflow */
+  bytes = x*y*ch;
+  if (bytes / y / ch != x) {
+    i_push_errorf(0, "integer overflow calculating image allocation");
+    return NULL;
+  }
 
   if (im == NULL)
     if ( (im=mymalloc(sizeof(i_img))) == NULL)
@@ -369,8 +377,9 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) {
   im->ysize    = y;
   im->channels = ch;
   im->ch_mask  = MAXINT;
-  im->bytes=x*y*im->channels;
-  if ( (im->idata=mymalloc(im->bytes)) == NULL) m_fatal(2,"malloc() error\n"); 
+  im->bytes=bytes;
+  if ( (im->idata=mymalloc(im->bytes)) == NULL) 
+    m_fatal(2,"malloc() error\n"); 
   memset(im->idata,0,(size_t)im->bytes);
   
   im->ext_data = NULL;
diff --git a/img16.c b/img16.c
index bb58475..2d79547 100644 (file)
--- a/img16.c
+++ b/img16.c
@@ -143,6 +143,7 @@ Creates a new 16-bit per sample image.
 =cut
 */
 i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) {
+  int bytes;
   mm_log((1,"i_img_16_new(x %d, y %d, ch %d)\n", x, y, ch));
 
   if (x < 1 || y < 1) {
@@ -153,13 +154,18 @@ i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) {
     i_push_errorf(0, "channels must be between 1 and %d", MAXCHANNELS);
     return NULL;
   }
+  bytes =  x * y * ch * 2;
+  if (bytes / y / ch / 2 != x) {
+    i_push_errorf(0, "integer overflow calculating image allocation");
+    return NULL;
+  }
   
   *im = IIM_base_16bit_direct;
   i_tags_new(&im->tags);
   im->xsize = x;
   im->ysize = y;
   im->channels = ch;
-  im->bytes = x * y * ch * 2;
+  im->bytes = bytes;
   im->ext_data = NULL;
   im->idata = mymalloc(im->bytes);
   if (im->idata) {
index d8e852f..8dc4ccc 100644 (file)
@@ -86,6 +86,8 @@ Creates a new double per sample image.
 =cut
 */
 i_img *i_img_double_new_low(i_img *im, int x, int y, int ch) {
+  int bytes;
+
   mm_log((1,"i_img_double_new(x %d, y %d, ch %d)\n", x, y, ch));
 
   if (x < 1 || y < 1) {
@@ -96,13 +98,18 @@ i_img *i_img_double_new_low(i_img *im, int x, int y, int ch) {
     i_push_errorf(0, "channels must be between 1 and %d", MAXCHANNELS);
     return NULL;
   }
+  bytes = x * y * ch * sizeof(double);
+  if (bytes / y / ch / sizeof(double) != x) {
+    i_push_errorf(0, "integer overflow calculating image allocation");
+    return NULL;
+  }
   
   *im = IIM_base_double_direct;
   i_tags_new(&im->tags);
   im->xsize = x;
   im->ysize = y;
   im->channels = ch;
-  im->bytes = x * y * ch * sizeof(double);
+  im->bytes = bytes;
   im->ext_data = NULL;
   im->idata = mymalloc(im->bytes);
   if (im->idata) {
index f397807..ecb1466 100644 (file)
--- a/palimg.c
+++ b/palimg.c
@@ -84,6 +84,7 @@ Currently 0 < maxpal <= 256
 */
 i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) {
   i_img_pal_ext *palext;
+  int bytes;
 
   i_clear_error();
   if (maxpal < 0 || maxpal > 256) {
@@ -98,6 +99,11 @@ i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) {
     i_push_errorf(0, "Channels must be positive and <= %d", MAXCHANNELS);
     return NULL;
   }
+  bytes = sizeof(i_palidx) * x * y;
+  if (bytes / y / sizeof(i_palidx) != x) {
+    i_push_errorf(0, "integer overflow calculating image allocation");
+    return NULL;
+  }
 
   memcpy(im, &IIM_base_8bit_pal, sizeof(i_img));
   palext = mymalloc(sizeof(i_img_pal_ext));
@@ -107,7 +113,7 @@ i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) {
   palext->last_found = -1;
   im->ext_data = palext;
   i_tags_new(&im->tags);
-  im->bytes = sizeof(i_palidx) * x * y;
+  im->bytes = bytes;
   im->idata = mymalloc(im->bytes);
   im->channels = channels;
   memset(im->idata, 0, im->bytes);
index 8e9c29f..b65ea85 100644 (file)
@@ -8,7 +8,7 @@
 use strict;
 
 my $loaded;
-BEGIN { $| = 1; print "1..85\n"; }
+BEGIN { $| = 1; print "1..93\n"; }
 END {print "not ok 1\n" unless $loaded;}
 use Imager qw(:handy :all);
 $loaded = 1;
@@ -250,6 +250,49 @@ okn($num++, !Imager->new(xsize=>1, ysize=>1, channels=>5),
 matchn($num++, Imager->errstr, qr/channels must be between 1 and 4/,
        "out of range channel message check");
 
+{
+  # https://rt.cpan.org/Ticket/Display.html?id=8213
+  # check for handling of memory allocation of very large images
+  # only test this on 32-bit machines - on a 64-bit machine it may
+  # result in trying to allocate 4Gb of memory, which is unfriendly at
+  # least and may result in running out of memory, causing a different
+  # type of exit
+  use Config;
+  if ($Config{ivsize} == 4) {
+    my $uint_range = 256 ** $Config{ivsize};
+    print "# range $uint_range\n";
+    my $dim1 = int(sqrt($uint_range))+1;
+    
+    my $im_b = Imager->new(xsize=>$dim1, ysize=>$dim1, channels=>1);
+    isn($num++, $im_b, undef, "integer overflow check - 1 channel");
+    
+    $im_b = Imager->new(xisze=>$dim1, ysize=>1, channels=>1);
+    okn($num++, $im_b, "but same width ok");
+    $im_b = Imager->new(xisze=>1, ysize=>$dim1, channels=>1);
+    okn($num++, $im_b, "but same height ok");
+    matchn($num++, Imager->errstr, qr/integer overflow/,
+           "check the error message");
+
+    # do a similar test with a 3 channel image, so we're sure we catch
+    # the same case where the third dimension causes the overflow
+    my $dim3 = int(sqrt($uint_range / 3))+1;
+    
+    $im_b = Imager->new(xsize=>$dim3, ysize=>$dim3, channels=>3);
+    isn($num++, $im_b, undef, "integer overflow check - 3 channel");
+    
+    $im_b = Imager->new(xisze=>$dim3, ysize=>1, channels=>3);
+    okn($num++, $im_b, "but same width ok");
+    $im_b = Imager->new(xisze=>1, ysize=>$dim3, channels=>3);
+    okn($num++, $im_b, "but same height ok");
+
+    matchn($num++, Imager->errstr, qr/integer overflow/,
+           "check the error message");
+  }
+  else {
+    skipn($num, 8, "don't want to allocate 4Gb");
+    $num += 8;
+  }
+}
 
 sub check_add {
   my ($base, $im, $color, $expected) = @_;
index d1a7c46..9ae5bcf 100644 (file)
@@ -1,6 +1,6 @@
 #!perl -w
 use strict;
-BEGIN { $| = 1; print "1..43\n"; }
+BEGIN { $| = 1; print "1..51\n"; }
 my $loaded;
 END {print "not ok 1\n" unless $loaded;}
 use Imager qw(:all :handy);
@@ -108,6 +108,50 @@ okn($num++, !Imager->new(xsize=>1, ysize=>1, bits=>16, channels=>5),
 matchn($num++, Imager->errstr, qr/channels must be between 1 and 4/,
        "and correct error message");
 
+{
+  # https://rt.cpan.org/Ticket/Display.html?id=8213
+  # check for handling of memory allocation of very large images
+  # only test this on 32-bit machines - on a 64-bit machine it may
+  # result in trying to allocate 4Gb of memory, which is unfriendly at
+  # least and may result in running out of memory, causing a different
+  # type of exit
+  use Config;
+  if ($Config{ivsize} == 4) {
+    my $uint_range = 256 ** $Config{ivsize};
+    print "# range $uint_range\n";
+    my $dim1 = int(sqrt($uint_range/2))+1;
+    
+    my $im_b = Imager->new(xsize=>$dim1, ysize=>$dim1, channels=>1, bits=>16);
+    isn($num++, $im_b, undef, "integer overflow check - 1 channel");
+    
+    $im_b = Imager->new(xisze=>$dim1, ysize=>1, channels=>1, bits=>16);
+    okn($num++, $im_b, "but same width ok");
+    $im_b = Imager->new(xisze=>1, ysize=>$dim1, channels=>1, bits=>16);
+    okn($num++, $im_b, "but same height ok");
+    matchn($num++, Imager->errstr, qr/integer overflow/,
+           "check the error message");
+
+    # do a similar test with a 3 channel image, so we're sure we catch
+    # the same case where the third dimension causes the overflow
+    my $dim3 = int(sqrt($uint_range / 3 / 2))+1;
+    
+    $im_b = Imager->new(xsize=>$dim3, ysize=>$dim3, channels=>3, bits=>16);
+    isn($num++, $im_b, undef, "integer overflow check - 3 channel");
+    
+    $im_b = Imager->new(xisze=>$dim3, ysize=>1, channels=>3, bits=>16);
+    okn($num++, $im_b, "but same width ok");
+    $im_b = Imager->new(xisze=>1, ysize=>$dim3, channels=>3, bits=>16);
+    okn($num++, $im_b, "but same height ok");
+
+    matchn($num++, Imager->errstr, qr/integer overflow/,
+           "check the error message");
+  }
+  else {
+    skipn($num, 8, "don't want to allocate 4Gb");
+    $num += 8;
+  }
+}
+
 sub NCF {
   return Imager::Color::Float->new(@_);
 }
index 87fdbbb..b3d8696 100644 (file)
@@ -1,6 +1,6 @@
 #!perl -w
 use strict;
-BEGIN { $| = 1; print "1..42\n"; }
+BEGIN { $| = 1; print "1..50\n"; }
 my $loaded;
 END {print "not ok 1\n" unless $loaded;}
 use Imager qw(:all :handy);
@@ -98,6 +98,50 @@ okn($num++, !Imager->new(xsize=>1, ysize=>1, bits=>'double', channels=>5),
 matchn($num++, Imager->errstr, qr/channels must be between 1 and 4/,
        "and correct message");
 
+{
+  # https://rt.cpan.org/Ticket/Display.html?id=8213
+  # check for handling of memory allocation of very large images
+  # only test this on 32-bit machines - on a 64-bit machine it may
+  # result in trying to allocate 4Gb of memory, which is unfriendly at
+  # least and may result in running out of memory, causing a different
+  # type of exit
+  use Config;
+  if ($Config{ivsize} == 4) {
+    my $uint_range = 256 ** $Config{ivsize};
+    my $dbl_size = $Config{doublesize} || 8;
+    my $dim1 = int(sqrt($uint_range/$dbl_size))+1;
+    
+    my $im_b = Imager->new(xsize=>$dim1, ysize=>$dim1, channels=>1, bits=>'double');
+    isn($num++, $im_b, undef, "integer overflow check - 1 channel");
+    
+    $im_b = Imager->new(xisze=>$dim1, ysize=>1, channels=>1, bits=>'double');
+    okn($num++, $im_b, "but same width ok");
+    $im_b = Imager->new(xisze=>1, ysize=>$dim1, channels=>1, bits=>'double');
+    okn($num++, $im_b, "but same height ok");
+    matchn($num++, Imager->errstr, qr/integer overflow/,
+           "check the error message");
+
+    # do a similar test with a 3 channel image, so we're sure we catch
+    # the same case where the third dimension causes the overflow
+    my $dim3 = int(sqrt($uint_range / 3 / $dbl_size))+1;
+    
+    $im_b = Imager->new(xsize=>$dim3, ysize=>$dim3, channels=>3, bits=>'double');
+    isn($num++, $im_b, undef, "integer overflow check - 3 channel");
+    
+    $im_b = Imager->new(xisze=>$dim3, ysize=>1, channels=>3, bits=>'double');
+    okn($num++, $im_b, "but same width ok");
+    $im_b = Imager->new(xisze=>1, ysize=>$dim3, channels=>3, bits=>'double');
+    okn($num++, $im_b, "but same height ok");
+
+    matchn($num++, Imager->errstr, qr/integer overflow/,
+           "check the error message");
+  }
+  else {
+    skipn($num, 8, "don't want to allocate 4Gb");
+    $num += 8;
+  }
+}
+
 sub NCF {
   return Imager::Color::Float->new(@_);
 }
index a247081..57e25cf 100644 (file)
@@ -4,7 +4,7 @@ use strict;
 my $loaded;
 BEGIN { 
   require "t/testtools.pl";
-  $| = 1; print "1..49\n";
+  $| = 1; print "1..57\n";
 }
 END { okx(0, "loading") unless $loaded; }
 use Imager;
@@ -136,6 +136,50 @@ okx(!Imager->new(xsize=>1, ysize=>1, type=>'paletted', channels=>5),
 matchx(Imager->errstr, qr/Channels must be positive and <= 4/,
        "and correct error message");
 
+{
+  # https://rt.cpan.org/Ticket/Display.html?id=8213
+  # check for handling of memory allocation of very large images
+  # only test this on 32-bit machines - on a 64-bit machine it may
+  # result in trying to allocate 4Gb of memory, which is unfriendly at
+  # least and may result in running out of memory, causing a different
+  # type of exit
+  use Config;
+  if ($Config{ivsize} == 4) {
+    my $uint_range = 256 ** $Config{ivsize};
+    my $dim1 = int(sqrt($uint_range))+1;
+    
+    my $im_b = Imager->new(xsize=>$dim1, ysize=>$dim1, channels=>1, type=>'paletted');
+    isx($im_b, undef, "integer overflow check - 1 channel");
+    
+    $im_b = Imager->new(xisze=>$dim1, ysize=>1, channels=>1, type=>'paletted');
+    okx($im_b, "but same width ok");
+    $im_b = Imager->new(xisze=>1, ysize=>$dim1, channels=>1, type=>'paletted');
+    okx($im_b, "but same height ok");
+    matchx(Imager->errstr, qr/integer overflow/,
+           "check the error message");
+
+    # do a similar test with a 3 channel image, so we're sure we catch
+    # the same case where the third dimension causes the overflow
+    # for paletted images the third dimension can't cause an overflow
+    # but make sure we didn't anything too dumb in the checks
+    my $dim3 = $dim1;
+    
+    $im_b = Imager->new(xsize=>$dim3, ysize=>$dim3, channels=>3, type=>'paletted');
+    isx($im_b, undef, "integer overflow check - 3 channel");
+    
+    $im_b = Imager->new(xisze=>$dim3, ysize=>1, channels=>3, type=>'paletted');
+    okx($im_b, "but same width ok");
+    $im_b = Imager->new(xisze=>1, ysize=>$dim3, channels=>3, type=>'paletted');
+    okx($im_b, "but same height ok");
+
+    matchx(Imager->errstr, qr/integer overflow/,
+           "check the error message");
+  }
+  else {
+    skipx(8, "don't want to allocate 4Gb");
+  }
+}
+
 sub coloreq {
   my ($left, $right, $comment) = @_;