xorl %eax, %eax

CVE-2009-0040: LibPNG Uninitialized Pointers

with 3 comments

This bug was disclosed on 19 February 2009 and reported by Travis Ormandy. LibPNG is a widely used library for PNG file format. Popular GNU/Linux distributions such as Ubuntu, Slackware, Debian etc. as well as commercial operating systems such as Apple Mac OS X have it installed by default. This issue affects LibPNG releases prior to 1.2.35 for the 1.2 branch and 1.0.43 for the 1.0. Here is the (first) vulnerable routine:

2 /* pngread.c - read a PNG file
     ...
1304 #ifndef PNG_NO_SEQUENTIAL_READ_SUPPORTED
1305 #if defined(PNG_INFO_IMAGE_SUPPORTED)
1306 void PNGAPI
1307 png_read_png(png_structp png_ptr, png_infop info_ptr,
1308                            int transforms,
1309                            voidp params)
1310 {
     ...
1440       for (row = 0; row < (int)info_ptr->height; row++)
1441       {
1442          info_ptr->row_pointers[row] = (png_bytep)png_malloc(png_ptr,
1443             png_get_rowbytes(png_ptr, info_ptr));
1444       }
1445    }
     ...
1456 }

This is part of the pngread.c file and as you can read at lines 1440-1444 it iterates through info_ptr->height and allocates space for each info_ptr->row_pointers[] member using png_malloc(). However, if the system cannot allocate memory for some elements during the iterations it will leave that info_ptr->row_pointers[] pointer uninitialized. The bytes to be allocated are retrieved using png_get_rowbytes() which is a function defined in pngget.c like this:

24 png_uint_32 PNGAPI
25 png_get_rowbytes(png_structp png_ptr, png_infop info_ptr)
26 {
27    if (png_ptr != NULL && info_ptr != NULL)
28       return(info_ptr->rowbytes);
29    else
30       return(0);
31 }

To patch this, the following fix was committed:

 #endif
+      png_memset(info_ptr->row_pointers, 0, info_ptr->height
+         * png_sizeof(png_bytep));
       for (row = 0; row < (int)info_ptr->height; row++)
-      {
          info_ptr->row_pointers[row] = (png_bytep)png_malloc(png_ptr,
             png_get_rowbytes(png_ptr, info_ptr));
-      }
    }

Using this, they initialize all the row_pointers to 0 using png_memset() and thus, even if some of the pointers cannot be allocated in the for loop. They will not cause an invalid free(3) call since they will be set to zero. The invalid free(3) occurs in png_malloc() wrapper. Their wrapper around malloc(3) is a complex implementation, but in case of failure in allocation, it executes this:

png_error(png_ptr, "Out Of Memory.");

This can be found in pngerror.c and more or less it does this in our case:

35 void PNGAPI
36 png_error(png_structp png_ptr, png_const_charp error_message)
37 {
      ...
89    /* If the custom handler doesn't exist, or if it returns,
90       use the default handler, which will not return. */
91    png_default_error(png_ptr, '');
92 }
      ...
81 }

And this function does this:

213 static void /* PRIVATE */
214 png_default_error(png_structp png_ptr, png_const_charp error_message)
215 {
      ...
243 #ifdef PNG_SETJMP_SUPPORTED
244    if (png_ptr)
245    {
246 #  ifdef USE_FAR_KEYWORD
247    {
248       jmp_buf jmpbuf;
249       png_memcpy(jmpbuf, png_ptr->jmpbuf, png_sizeof(jmp_buf));
250       longjmp(jmpbuf, 1);
251    }
252 #  else
253    longjmp(png_ptr->jmpbuf, 1);
254 #  endif
255    }
256 #else
257    PNG_ABORT();
258 #endif
259 #ifdef PNG_NO_CONSOLE_IO
260    error_message = error_message; /* make compiler happy */
261 #endif
262 }

So, it will attempt to longjmp(3) at the specified jmpbuf. This is defined in pngwrite.c like this:

447 /* Alternate initialize png_ptr structure, and allocate any memory needed */
448 png_structp PNGAPI
449 png_create_write_struct_2(png_const_charp user_png_ver, png_voidp error_ptr,
450    png_error_ptr error_fn, png_error_ptr warn_fn, png_voidp mem_ptr,
451    png_malloc_ptr malloc_fn, png_free_ptr free_fn)
452 {
      ...
484    if (setjmp(png_ptr->jmpbuf))
485 #endif
486    {
487       png_free(png_ptr, png_ptr->zbuf);
488        png_ptr->zbuf=NULL;
489       png_destroy_struct(png_ptr);
490       return (NULL);
491    }

The functions at lines 487 and 489 will attempt to free(3) the uninitialized pointers. The exact same vulnerability was also present at png_build_gamma_table() from pngrtran.c as you can see here:

2 /* pngrtran.c - transforms the data in a row for PNG readers
      ...
4020 /* We build the 8- or 16-bit gamma tables here.  Note that for 16-bit
4021  * tables, we don't make a full table if we are reducing to 8-bit in
4022  * the future.  Note also how the gamma_16 tables are segmented so that
4023  * we don't need to allocate > 64K chunks for a full 16-bit table.
4024  */
4025 void /* PRIVATE */
4026 png_build_gamma_table(png_structp png_ptr)
4027 {
      ...
4131      if (png_ptr->transformations & (PNG_16_TO_8 | PNG_BACKGROUND))
4132      {
4133         double fin, fout;
4134         png_uint_32 last, max;
4135
4136         for (i = 0; i < num; i++)
4137         {
4138            png_ptr->gamma_16_table[i] = (png_uint_16p)png_malloc(png_ptr,
4139               (png_uint_32)(256 * png_sizeof(png_uint_16)));
4140         }
      ...
4164      else
4165      {
4166         for (i = 0; i < num; i++)
4167         {
4168            png_ptr->gamma_16_table[i] = (png_uint_16p)png_malloc(png_ptr,
4169               (png_uint_32)(256 * png_sizeof(png_uint_16)));
      ...
4214         for (i = 0; i < num; i++)
4215         {
4216            png_ptr->gamma_16_from_1[i] = (png_uint_16p)png_malloc(png_ptr,
4217               (png_uint_32)(256 * png_sizeof(png_uint_16)));
      ...
4231 }


This is the same concept in the initialization of gamma_16_table array. To patch this, the same approach was used and the array was zeroed out before entering the loop like this:

+        png_memset(png_ptr->gamma_16_table, 0, num * png_sizeof(png_uint_16p));
+
         for (i = 0; i < num; i++)

and:

+        png_memset(png_ptr->gamma_16_to_1, 0, num * png_sizeof(png_uint_16p));
+
         for (i = 0; i < num; i++)
         {

And at last:

+        png_memset(png_ptr->gamma_16_from_1, 0,
+           num * png_sizeof(png_uint_16p));
+
         for (i = 0; i < num; i++)
        {


A user can therefore construct a PNG image that will make png_malloc() fail in one of the above cases and return to its handler which will attempt to free(3) the uninitialized pointer(s). Clearly, this can lead to code execution and since this library is used by popular web browsers like Mozilla Firefox you can use this for remote code execution through a malicious PNG image.

Written by xorl

May 13, 2009 at 12:08

Posted in bugs

3 Responses

Subscribe to comments with RSS.

  1. Actually, this was discovered by me, Glenn is the libpng maintainer who just informed the mozilla guys they needed to update :-)

    http://www.mozilla.org/security/announce/2009/mfsa2009-10.html

    http://sourceforge.net/mailarchive/message.php?msg_name=e56ccc8f0902181726i200f4bf0n20d919473ec409b7%40mail.gmail.com

    Tavis Ormandy

    May 13, 2009 at 16:29

  2. As always, thanks for a great write-up!

    Just for clarity’s sake :) this bug was discovered and reported by Tavis Ormandy. (The png maintainer passed it along to Mozilla himself.)

    http://www.mozilla.org/security/announce/2009/mfsa2009-10.html
    http://sourceforge.net/mailarchive/message.php?msg_name=e56ccc8f0902181726i200f4bf0n20d919473ec409b7%40mail.gmail.com

    Will Drewry

    May 13, 2009 at 16:38

  3. Oh… thanks for that, I fixed it.

    xorl

    May 13, 2009 at 16:43


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s