Bug #73645 [Opn->Csd]: version_compare illegal write access

From: Date: Tue, 06 Dec 2016 06:17:15 +0000
Subject: Bug #73645 [Opn->Csd]: version_compare illegal write access
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-205796@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 +Status: Closed Type: Bug Package: *General Issues Operating System: Linux PHP Version: 7.0.13 Block user comment: N Private report: N New Comment: 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 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

« previous php.bugs (#205796) next »