php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #78069 Out-of-bounds read in iconv.c:_php_iconv_mime_decode() due to integer overflow
Submitted: 2019-05-26 14:36 UTC Modified: 2019-05-27 23:48 UTC
From: maris dot adam at gmail dot com Assigned: stas (profile)
Status: Closed Package: ICONV related
PHP Version: 7.1.29 OS: Linux
Private report: No CVE-ID: 2019-11039
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: maris dot adam at gmail dot com
New email:
PHP Version: OS:

 

 [2019-05-26 14:36 UTC] maris dot adam at gmail dot com
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

Patches

iconv_patch.diff (last revision 2019-05-26 14:36 UTC by maris dot adam at gmail dot com)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [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
Thanks for reporting this issue, the fine analysis and the good
patch!

Stas, could you please handle this for the upcoming GA releases?
 [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
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=7cf7148a8f8f4f55fb04de2a517d740bb6253eac
Log: Fix bug #78069 - Out-of-bounds read in iconv.c:_php_iconv_mime_decode() due to integer overflow
 [2019-05-27 23:49 UTC] stas@php.net
-Status: Verified +Status: Closed
 [2019-05-27 23:49 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=7cf7148a8f8f4f55fb04de2a517d740bb6253eac
Log: Fix bug #78069 - Out-of-bounds read in iconv.c:_php_iconv_mime_decode() due to integer overflow
 [2019-05-28 07:07 UTC] cmb@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=70523ce41ff400ea00343a03f297332cb1f1b77b
Log: Fix bug #78069 - Out-of-bounds read in iconv.c:_php_iconv_mime_decode() due to integer overflow
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 10:01:29 2024 UTC