php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #73645 version_compare illegal write access
Submitted: 2016-12-03 17:08 UTC Modified: 2016-12-06 06:17 UTC
From: fernando at null-life dot com Assigned:
Status: Closed Package: *General Issues
PHP Version: 7.0.13 OS: Linux
Private report: No CVE-ID:
 [2016-12-03 17:08 UTC] fernando at null-life dot com
Description:
------------
When version1 or version2 parameter length need more than 32 bits to be stored, php_canonicalize_version function calls to safe_emalloc with size parameter casted to 32 bits. Then, write of bounds happens when version parameter is copied to buffer.


--------------------------------------------------------------------------------------
Source code:
https://github.com/php/php-src/blob/PHP-7.0.14/ext/standard/versioning.c#L33

PHPAPI char *
php_canonicalize_version(const char *version)
{
    int len = strlen(version);
    char *buf = safe_emalloc(len, 2, 1), *q, lp, lq;
    const char *p;

    if (len == 0) {
        *buf = '\0';
        return buf;
    }

    p = version;
    q = buf;
    *q++ = lp = *p++;

    while (*p) {
/*  s/[-_+]/./g;
 *  s/([^\d\.])([^\D\.])/$1.$2/g;
 *  s/([^\D\.])([^\d\.])/$1.$2/g;
 */
#define isdig(x) (isdigit(x)&&(x)!='.')
#define isndig(x) (!isdigit(x)&&(x)!='.')
#define isspecialver(x) ((x)=='-'||(x)=='_'||(x)=='+')

		lq = *(q - 1);
		if (isspecialver(*p)) {
			if (lq != '.') {
				*q++ = '.';
			}
		} else if ((isndig(lp) && isdig(*p)) || (isdig(lp) && isndig(*p))) {
			if (lq != '.') {
				*q++ = '.';
			}
			*q++ = *p;
		} else if (!isalnum(*p)) {
			if (lq != '.') {
				*q++ = '.';
			}
		} else {
			*q++ = *p;   // write out of bounds
		}
		lp = *p++;
    }
    *q++ = '\0';
    return buf;
}



GDB output:

gdb -q --args /home/operac/build5/bin/php -n poc.php
Reading symbols from /home/operac/build5/bin/php...done.
(gdb) b php_canonicalize_version
Breakpoint 1 at 0xc39cb6: file /home/operac/build5/php-src/ext/standard/versioning.c, line 36.
(gdb) r
Starting program: /home/operac/build5/bin/php -n poc.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, php_canonicalize_version (version=0x7ffeeec00018 'x' <repeats 200 times>...) at /home/operac/build5/php-src/ext/standard/versioning.c:36
36          int len = strlen(version);
(gdb) b _safe_emalloc
Breakpoint 2 at 0xdc7637: file /home/operac/build5/php-src/Zend/zend_alloc.c, line 2516.
(gdb) b 37
Breakpoint 3 at 0xc39cc5: file /home/operac/build5/php-src/ext/standard/versioning.c, line 37.
(gdb) c
Continuing.

Breakpoint 3, php_canonicalize_version (version=0x7ffeeec00018 'x' <repeats 200 times>...) at /home/operac/build5/php-src/ext/standard/versioning.c:37
37          char *buf = safe_emalloc(len, 2, 1), *q, lp, lq;
(gdb) p len
$1 = 1                 // 0x100000001 casted to 1
(gdb) c
Continuing.

Breakpoint 2, _safe_emalloc (nmemb=1, size=2, offset=1, __zend_filename=0x16f1c60 "/home/operac/build5/php-src/ext/standard/versioning.c", __zend_lineno=37, __zend_orig_filename=0x0, __zend_orig_lineno=0)
    at /home/operac/build5/php-src/Zend/zend_alloc.c:2516
2516            return emalloc_rel(zend_safe_address_guarded(nmemb, size, offset));
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x0000000000c3a3bf in php_canonicalize_version (version=0x7ffeeec00018 'x' <repeats 200 times>...) at /home/operac/build5/php-src/ext/standard/versioning.c:73
73                              *q++ = *p;           // write out of bounds




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

ini_set('memory_limit', -1);

$v1 = str_repeat('x', 0x100000001);
version_compare($v1, PHP_VERSION);

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

Actual result:
--------------
ASan output:


ASAN:SIGSEGV
=================================================================
==15966==ERROR: AddressSanitizer: SEGV on unknown address 0x7f88d1200000 (pc 0x000000c3a3bf bp 0x7ffec149db70 sp 0x7ffec149db40 T0)
    #0 0xc3a3be in php_canonicalize_version /home/operac/build5/php-src/ext/standard/versioning.c:73
    #1 0xc3aa01 in php_version_compare /home/operac/build5/php-src/ext/standard/versioning.c:145
    #2 0xc3b44e in zif_version_compare /home/operac/build5/php-src/ext/standard/versioning.c:222
    #3 0xf277dc in ZEND_DO_ICALL_SPEC_HANDLER /home/operac/build5/php-src/Zend/zend_vm_execute.h:586
    #4 0xf26872 in execute_ex /home/operac/build5/php-src/Zend/zend_vm_execute.h:414
    #5 0xf26aed in zend_execute /home/operac/build5/php-src/Zend/zend_vm_execute.h:458
    #6 0xe49c66 in zend_execute_scripts /home/operac/build5/php-src/Zend/zend.c:1427
    #7 0xd0b9d8 in php_execute_script /home/operac/build5/php-src/main/main.c:2494
    #8 0x1058a1c in do_cli /home/operac/build5/php-src/sapi/cli/php_cli.c:974
    #9 0x105a82c in main /home/operac/build5/php-src/sapi/cli/php_cli.c:1344
    #10 0x7f88d6b4b82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #11 0x430a88 in _start (/home/operac/build5/bin/php+0x430a88)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/operac/build5/php-src/ext/standard/versioning.c:73 php_canonicalize_version
==15966==ABORTING


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-12-06 06:17 UTC] stas@php.net
-Type: Security +Type: Bug
 [2016-12-06 06:17 UTC] stas@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=1d59ed75246c7d05688accc349fda70cbee90428
Log: Fix bug #73645 - int/size_t confusion
 [2016-12-06 06:17 UTC] stas@php.net
-Status: Open +Status: Closed
 [2016-12-06 16:12 UTC] ab@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=a92718868d6d491b40d994ec8703aa3ebe6f9383
Log: Fix bug #73645 - int/size_t confusion
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Sun Jul 23 14:01:36 2017 UTC