Actually, it's slightly more complicated than that. Libpng uses callbacks to aid with progressive reading of a png. That way, as the image rows become available, the application can handle them. The root problem lies in the fact that libpng versions prior to the fix would call the row_callback() function in the application for _every_ row without checking to see if more rows had been processed than the declared height in the IHDR chunk (which is what memory allocations are [usually] based on). Since libpng didn't check the number of rows processed (or the number of times the row_callback() was called), the last place to catch this bug would be in the application. If the application checked how many times its callback had been called against the height of the png stored in the png_ptr, it could bail when too many rows were received. But, libpng has been around for quite a while (15+ years), so everyone trusts it and never does their own checks, merely copying the inflated image rows into their own buffers, using a calculation based on their base buffer address, image width, row number, and the number of bytes per pixel.
Below is a snippet of Firefox's row_callback function. The statement that copies the image row data to the application's buffer is on line 822:
// Firefox 3.6.3 // modules/libpr0n/decoders/png/nsPNGDecoder.cpp <-- who named this module?? 725 void 726 row_callback(png_structp png_ptr, png_bytep new_row, 727 png_uint_32 row_num, int pass) 728 { ... 762 if (new_row) { ... 791 switch (decoder->format) { ... 819 case gfxASurface::ImageFormatARGB32: 820 { 821 for (PRUint32 x=width; x>0; --x) { 822 *cptr32++ = GFX_PACKED_PIXEL(line[3], line[0], line[1], line[2]); 823 if (line[3] != 0xff) 824 rowHasNoAlpha = PR_FALSE; 825 line += 4; 826 } 827 } 828 break; ... 833 } ... 851 } 852 }
// Google Chrome, r44471 // gfx/codec/png_codec.cc 338 void DecodeRowCallback(png_struct* png_ptr, png_byte* new_row, 339 png_uint_32 row_num, int pass) { 340 PngDecoderState* state = static_cast( 341 png_get_progressive_ptr(png_ptr)); 342 343 DCHECK(pass == 0) << "We didn't turn on interlace handling, but libpng is " 344 "giving us interlaced data."; 345 if (static_cast (row_num) > state->height) { 346 NOTREACHED() << "Invalid row"; 347 return; 348 } 349 350 unsigned char* base = NULL; 351 if (state->bitmap) 352 base = reinterpret_cast (state->bitmap->getAddr32(0, 0)); 353 else if (state->output) 354 base = &state->output->front(); 355 356 unsigned char* dest = &base[state->width * state->output_channels * row_num]; 357 if (state->row_converter) 358 state->row_converter(new_row, state->width, dest, &state->is_opaque); 359 else 360 memcpy(dest, new_row, state->width * state->output_channels); 361 }
First, the function png_process_data() is called by the application:
// pngpread.c 30 void PNGAPI 31 png_process_data(png_structp png_ptr, png_infop info_ptr, 32 png_bytep buffer, png_size_t buffer_size) 33 { 34 if (png_ptr == NULL || info_ptr == NULL) 35 return; 36 37 png_push_restore_buffer(png_ptr, buffer, buffer_size); 38 39 while (png_ptr->buffer_size) 40 { 41 png_process_some_data(png_ptr, info_ptr); 42 } 43 }
// pngpread.c 45 /* What we do with the incoming data depends on what we were previously 46 * doing before we ran out of data... 47 */ 48 void /* PRIVATE */ 49 png_process_some_data(png_structp png_ptr, png_infop info_ptr) 50 { 51 if (png_ptr == NULL) 52 return; 53 54 switch (png_ptr->process_mode) 55 { ... 62 case PNG_READ_CHUNK_MODE: 63 { 64 png_push_read_chunk(png_ptr, info_ptr); 65 break; 66 } 67 68 case PNG_READ_IDAT_MODE: 69 { 70 png_push_read_IDAT(png_ptr); 71 break; 72 } ... 109 } 110 }
// pngpread.c 735 void /* PRIVATE */ 736 png_push_read_IDAT(png_structp png_ptr) 737 { 738 PNG_IDAT; ... 765 if (png_ptr->idat_size && png_ptr->save_buffer_size) 766 { 767 png_size_t save_size; 768 769 if (png_ptr->idat_size < (png_uint_32)png_ptr->save_buffer_size) 770 { 771 save_size = (png_size_t)png_ptr->idat_size; 772 773 /* Check for overflow */ 774 if ((png_uint_32)save_size != png_ptr->idat_size) 775 png_error(png_ptr, "save_size overflowed in pngpread"); 776 } 777 else 778 save_size = png_ptr->save_buffer_size; 779 780 png_calculate_crc(png_ptr, png_ptr->save_buffer_ptr, save_size); 781 782 if (!(png_ptr->flags & PNG_FLAG_ZLIB_FINISHED)) 783 png_process_IDAT_data(png_ptr, png_ptr->save_buffer_ptr, save_size); 784 785 png_ptr->idat_size -= save_size; 786 png_ptr->buffer_size -= save_size; 787 png_ptr->save_buffer_size -= save_size; 788 png_ptr->save_buffer_ptr += save_size; 789 } ... 826 }
// pngpread.c 828 void /* PRIVATE */ 829 png_process_IDAT_data(png_structp png_ptr, png_bytep buffer, 830 png_size_t buffer_length) 831 { 832 int ret; 833 834 if ((png_ptr->flags & PNG_FLAG_ZLIB_FINISHED) && buffer_length) 835 png_benign_error(png_ptr, "Extra compression data"); 836 837 png_ptr->zstream.next_in = buffer; 838 png_ptr->zstream.avail_in = (uInt)buffer_length; 839 for (;;) 840 { 841 ret = inflate(&png_ptr->zstream, Z_PARTIAL_FLUSH); 842 if (ret != Z_OK) 843 { 844 if (ret == Z_STREAM_END) 845 { 846 if (png_ptr->zstream.avail_in) 847 png_benign_error(png_ptr, "Extra compressed data"); 848 849 if (!(png_ptr->zstream.avail_out)) 850 { 851 png_push_process_row(png_ptr); 852 } 853 854 png_ptr->mode |= PNG_AFTER_IDAT; 855 png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED; 856 break; 857 } 858 else if (ret == Z_BUF_ERROR) 859 break; 860 861 else 862 png_error(png_ptr, "Decompression Error"); 863 } 864 if (!(png_ptr->zstream.avail_out)) 865 { 866 if (( 867 #ifdef PNG_READ_INTERLACING_SUPPORTED 868 png_ptr->interlaced && png_ptr->pass > 6) || 869 (!png_ptr->interlaced && 870 #endif 871 png_ptr->row_number == png_ptr->num_rows)) 872 { 873 if (png_ptr->zstream.avail_in) 874 png_warning(png_ptr, "Too much data in IDAT chunks"); 875 png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED; 876 break; 877 } 878 png_push_process_row(png_ptr); 879 png_ptr->zstream.avail_out = 880 (uInt) PNG_ROWBYTES(png_ptr->pixel_depth, 881 png_ptr->iwidth) + 1; 882 png_ptr->zstream.next_out = png_ptr->row_buf; 883 } 884 885 else 886 break; 887 } 888 }
// pngpread.c 890 void /* PRIVATE */ 891 png_push_process_row(png_structp png_ptr) 892 { 893 png_ptr->row_info.color_type = png_ptr->color_type; 894 png_ptr->row_info.width = png_ptr->iwidth; 895 png_ptr->row_info.channels = png_ptr->channels; 896 png_ptr->row_info.bit_depth = png_ptr->bit_depth; 897 png_ptr->row_info.pixel_depth = png_ptr->pixel_depth; 898 899 png_ptr->row_info.rowbytes = PNG_ROWBYTES(png_ptr->row_info.pixel_depth, 900 png_ptr->row_info.width); 901 902 png_read_filter_row(png_ptr, &(png_ptr->row_info), 903 png_ptr->row_buf + 1, png_ptr->prev_row + 1, 904 (int)(png_ptr->row_buf[0])); 905 906 png_memcpy(png_ptr->prev_row, png_ptr->row_buf, png_ptr->rowbytes + 1); 907 908 if (png_ptr->transformations || (png_ptr->flags&PNG_FLAG_STRIP_ALPHA)) 909 png_do_read_transformations(png_ptr); 910 911 #ifdef PNG_READ_INTERLACING_SUPPORTED ... 1088 #endif 1089 { 1090 png_push_have_row(png_ptr, png_ptr->row_buf + 1); 1091 png_read_push_finish_row(png_ptr); 1092 } 1093 }
// pngpread.c 1682 void /* PRIVATE */ 1683 png_push_have_row(png_structp png_ptr, png_bytep row) 1684 { 1685 if (png_ptr->row_fn != NULL) 1686 (*(png_ptr->row_fn))(png_ptr, row, png_ptr->row_number, 1687 (int)png_ptr->pass); 1688 }
// pngpread.c 1095 void /* PRIVATE */ 1096 png_read_push_finish_row(png_structp png_ptr) 1097 { ... 1117 png_ptr->row_number++; ... 1157 }
// pngpread.c, libpng-1.4.3 827 void /* PRIVATE */ 828 png_process_IDAT_data(png_structp png_ptr, png_bytep buffer, 829 png_size_t buffer_length) 830 { ... 845 while (png_ptr->zstream.avail_in > 0 && 846 !(png_ptr->flags & PNG_FLAG_ZLIB_FINISHED)) 847 { ... 891 /* Did inflate output any data? */ 892 if (png_ptr->zstream.next_out != png_ptr->row_buf) 893 { 894 /* Is this unexpected data after the last row? 895 * If it is, artificially terminate the LZ output 896 * here. 897 */ 898 if (png_ptr->row_number >= png_ptr->num_rows || 899 png_ptr->pass > 6) 900 { 901 /* Extra data. */ 902 png_warning(png_ptr, "Extra compressed data in IDAT"); 903 png_ptr->flags |= PNG_FLAG_ZLIB_FINISHED; 904 /* Do no more processing; skip the unprocessed 905 * input check below. 906 */ 907 return; 908 } ... 918 } ... 926 }
I think this is a prime example about trusting third-party code. Like a firearm safety instructor told me once about using the safety, "we use them, but we don't trust them". I think that is how third-party code should be used.
A pleasure to be the first one! ;)
ReplyDeleteSo, the question is - how many of the other common applications are vulnerable and - does every medium/big vendor remember to use the latest libpng version while releasing product updates?
GW @ the discovery and analysis.
Excellent research, keep up the good work ;]
ReplyDeleteNice blog post d0c. Of course, actually exploiting this reliably is likely to be fairly tricky. Have you tried at all? I'm looking forward to your next post like this!
ReplyDelete@j00ru - here's a small list I've put together of apps that use libpng (I haven't tested all of them to see if they are vulnerable though). I used searches like "inurl:svn libpng" and "dependencies libpng" to find most of them:
ReplyDeleteFirefox
Thunderbird
Chrome
Inkscape
OpenOffice
cafu
avidemux
ffmpeg
media player classic
R
ghostscript
Qt
gnome
php
Java
Harmony
gimp
FalconView (some web browser I guess?)
As far as I can tell, it seems like the only way an application can be vulnerable is if they use the progressive reading functionality in libpng. If an app doesn't use it, then I think it should be safe (which means most of the apps above _probably_ aren't vulnerable, excluding the first three, which I know are). Also, just FYI, pngpread.c is for progressive reading and pngread.c is for non-progressive reading.
@jduck - That's a work in progress right now. A while back I was trying to get it working with Firefox, but didn't have too much luck and got burnt out on it. I started working on it again though - if I get something working I'll post it here :^)
Hi !
ReplyDeleteA small correction :
prefixes | opcode | imm | disasm
----------+--------+----------+------------------------
| 04 | aa | add al,aa
66 | 05 | aaaa | add ax,aaaa
| 05 | aaaaaaaa | add eax,aaaaaaaa
--
Kad