Sec Bug->Bug #73645 [Opn]: version_compare illegal write access
| From: | stas@php.net | Date: | Tue, 06 Dec 2016 06:17:08 +0000 |
| Subject: | Sec Bug->Bug #73645 [Opn]: version_compare illegal write access | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-205795@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=73645&edit=1
ID: 73645
Updated by: stas@php.net
Reported by: fernando at null-life dot com
Summary: version_compare illegal write access
Status: Open
-Type: Security
+Type: Bug
Package: *General Issues
Operating System: Linux
PHP Version: 7.0.13
Block user comment: N
Private report: Y
Previous Comments:
------------------------------------------------------------------------
[2016-12-03 17:08:29] 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
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=73645&edit=1