PHP 5.3.3 hangs on numeric value 2.2250738585072011e-308
exploringbinary.com
exploringbinary.com
This problem occurs due to IA-32's 80-bit floating point arithmetic. The simple fix: add a "-ffloat-store" flag to your CFLAGS.
The problematic function, zend_strtod, seems to parse the mantissa (2.225...011 part) and the exponent (-308 part) separately, calculate the approximation of m*10^e and successively improve that approximation until the error becomes less than 0.5ulp. The problem is that this particular number causes the infinite loop (i.e. the iteration does not improve the error at all) in 80-bit FP, but does not in 64-bit FP. Since x86-64 in general uses the SSE2 instruction set (with 64-bit FP) instead of the deprecated x87 it does not have this problem.
Yes yes yes yes yes, but my question is not "what happens in the PHP" but why does that happen. Shouldn't there just be an algorithm that just runs down the number and produces the correct float without any approximation step? Floats and doubles may be inaccurate but the inaccuracy in this case is purely deterministic.
Here's the strtod.c in tcl, with various exciting things in the copyright: http://www.opensource.apple.com/source/tcl/tcl-14/tcl/compat... Unless I've missed it, there's no convergence testing there, it just gets on with the conversion; all the if, while, and gotos seem to be focused on the matter of parsing, not checking error sizes. Is the PHP way faster? Is the linked code somehow inaccurate? Is the PHP way just insane?
the apple engineers simply rely on the compiler library: they collect the mantissa digits into up to 2 integers, thus generating an exact binary representation for mantissse of up to 18 decimal digits. thereafter they coalesce these integers into a double variable having an implicit cast from int to double - that's where the library or compiler-generated sequence of machine instructions take over.
the pgp guys actually do the same thing. however, after this step they continue to adjust the result for obtaining the best approximation in the sense above. it can be proven mathematically ([1]) that this task _cannot_ be performed for all inputs using arithmetic with any given precision. [1] also provides a near-optimal non-iterative solution which the author of the php conversion routine has improved upon. however the computations involve floating point (processor) arithmetic which suffers from a specific error in intel fpus. due to this error intermediate results are altered in an unfortunate way so that the adjustment algorithm no longerterminated in spite of the theoretical guarantee.
some more words about the 18-digit mantissa mentioned at the beginning. the apple code cuts off the mantissa after 18 decimal digits and its authors claim this operation won't affect the result. that would not be a problem if the input strings would be given with normalized mantissae (first digit not a zero), as the error was introduced in the 18th decimal / approximately 60th binary digit while ieee 754 double format only caters for 52 matissa digits. as the strtod.c docs do not detail the 'internal fp representation', this might be problematic (it would be eg. for 80-bit extended precision on x87 coprocessors featuring 64 mantissa bits). however, as they allow non-normalized mantissa values, their conversion runs foul on strings with 'many' leading zeros, ie they'd convert 0.0000000000000000009999E+0 to 0.
hope all this makes kind of sense.
greets, carsten
[1] http://dx.doi.org/10.1145/989393.989430 William D. Clinger, How to read floating point numbers accurately, ACM SIGPLAN Notices, v.25 n.6, p.92-101, Jun. 1990
[2] http://en.wikipedia.org/wiki/Double_precision_floating-point... fp formats for starters
http://en.wikipedia.org/wiki/Floating_point#Accuracy_problem...
Its a quick+dirty fix for site-owners that cannot immediately upgrade php.
Here's what you do. Step 1: compile PHP with debugging symbols. Then run the test case in GDB:
$ gdb `which php`
(gdb) set args testcase.php
(gdb) run
<program hangs>
Then hit C-c, and see where the program is:
^C
Program received signal SIGINT, Interrupt.
0x0000000000703898 in ?? () #0 0x0000000000703898 in ?? ()
#1 0x00000000006aae40 in execute ()
#2 0x00007ffff4400116 in ?? () from /usr/lib/php5/20090626/suhosin.so
#3 0x000000000068290d in zend_execute_scripts ()
#4 0x000000000062e1a8 in php_execute_script ()
#5 0x000000000071317a in ?? ()
#6 0x00007ffff5475c4d in __libc_start_main (main=<value optimized out>, argc=<value optimized out>, ubp_av=<value optimized out>,
init=<value optimized out>, fini=<value optimized out>, rtld_fini=<value optimized out>, stack_end=0x7fffffffe9c8)
at libc-start.c:228
#7 0x000000000042d4b9 in _start ()
Now you have some idea of where to look. (Note: this is not the actual bug, as I can't reproduce it on my machine. This is <?php while(1){} ?>, which is just as good for demonstration purposes. Also, no debugging symbols, so we don't really know what's going on.)No offense, but this is like programming 101.
Thats a bit harsh. You could have happily posted the above (useful) comment without that dig at the end.
It might be if you grew up in a C/C++ environment, many people these days start with scripting languages and don't venture out (myself included, but learning C is my new year's resolution).
require 'rubygems'
require 'ruby-debug'
Debugger.start
Signal.trap('INT') { debugger } # this line makes the debugger run when you hit ctrl-c
# YOUR CODE HERE INSTEAD!
loop {}
Simply hit ctrl-C (sometimes twice) when your code is hung or whatever and then issue the `where` command to rdb. Just make sure you have the ruby-debug gem installed.It's actually easier, as you obviously don't have to worry about debugging symbols. You can also attach rdb to running interpreter processes and all that good stuff.
http://bashdb.sourceforge.net/ruby-debug.html
EDIT: I've just realised that this will obviously only catch bugs in ruby code. For bugs in the ruby interpreter itself, like the php bug under discussion, you will need to use gdb on the interpreter binary like jrockway showed. Still the point is that you shouldn't consider this stuff over your head just because you only hack dynamic languages :)
that doesn't mean that we won't ever need fax again, or morse code, it still exists and has it's place, but for the majority of users it's not needed, and the same will happen with all technology, including programming. eventually almost noone will need or use something low level like C, just like most people right now don't need or use assembly, or even lower level, machine code, or even lower level... I don't even know what's lower level. Point being as things move along the lower level, while still being there, is understood and used by less and less people.
I.e.: its like a world where all our cellphones/email still, under the covers, run over fax, and we still need a fax machine in every house to make this possible. =P
Without people to write the languages in C, and without people to hack on the kernel in C, there will be no more languages, and existing C based languages will get no more features.
The best you can do now is write one high-level language in another, and that tends to be reasonably slow.
Just because you don't understand something, or because you don't know anybody who does, that doesn't mean that technology is disappearing, it just means you're in a selective circle.
Debug trace:
#0 0x0832257f in mult (a=0xe1931e82, b=0x8781590)
at /usr/src/php-5.3.3/Zend/zend_strtod.c:720
#1 0x08322757 in pow5mult (b=0x8781590, k=1)
at /usr/src/php-5.3.3/Zend/zend_strtod.c:803
#2 0x08324443 in zend_strtod (s00=0xb7a7d01d "e-308;\n?>\n", se=0x0)
at /usr/src/php-5.3.3/Zend/zend_strtod.c:2352
#3 0x082e03ce in lex_scan (zendlval=0xbf94dd34, tsrm_ls=0x8648050)
at Zend/zend_language_scanner.l:1382
#4 0x082fa849 in zendlex (zendlval=0xbf94dd30, tsrm_ls=0x8648050)
at /usr/src/php-5.3.3/Zend/zend_compile.c:4942
#5 0x082dcc47 in zendparse (tsrm_ls=0x8648050)
at /usr/src/php-5.3.3/Zend/zend_language_parser.c:3280
#6 0x082dd232 in compile_file (file_handle=0xbf9502d0, type=8,
tsrm_ls=0x8648050) at Zend/zend_language_scanner.l:354
#7 0x081ad3cc in phar_compile_file (file_handle=0xbf9502d0, type=8,
tsrm_ls=0x8648050) at /usr/src/php-5.3.3/ext/phar/phar.c:3393
#8 0x0830acc5 in zend_execute_scripts (type=8, tsrm_ls=0x8648050, retval=0x0,
file_count=3) at /usr/src/php-5.3.3/Zend/zend.c:1186
#9 0x082b660f in php_execute_script (primary_file=0xbf9502d0,
tsrm_ls=0x8648050) at /usr/src/php-5.3.3/main/main.c:2260
#10 0x08388893 in main (argc=2, argv=0xbf9503b4)
at /usr/src/php-5.3.3/sapi/cli/php_cli.c:1192This is really serious. In fact, I’ve just tested if the problem happens for GET passed values and it does. Not all the passed data to a website is treated as a number, so not all websites with the PHP versions and configuration that could fail with this bug will be vulnerable, but definitely there is going to be a huge amount of websites that will do. This is really scaring.
I hope the PHP team patch it soon.
Meanwhile, a possible workaround would be adding this line at the very top of the execution of php website:
if (strpos(str_replace('.', '', serialize($GLOBALS)), '22250738585072011')!==false) die();
This will stop execution if any decimal version of the number were passed as parameter. Note that 222.50738585072011e-310 cause problems too, and any of the other possibilities to write it.
Do you know if there are any other possible ways to write the number that causes trouble too?
Apache2 (mod_php) spun at 100% cpu usage.
This suggested workaround works for me. Obviously, it's a limited bandaid, but thanks for suggesting it.
Does this just crash the local process for that request or will it also cause problems for other requests? Anyone able to test this?
In Windows it leaves a zombie resource putting the cpu to 100%, so it doesn't seems to be a nice thing...
I usually do something like:
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
It has to be treated as a float to be a problem, not as an int.- Must be one of the PHP 5.3.3 versions with the bug, where very very few web hosts are running such a recent version (5.0, 5.1 and 5.2 branches are much more common)
- Must be a 32-bit version, no bug in 64-bit
- The PHP program must try to use the input as a number
- PHP 5.3.3-1ubuntu9.1 i686 build (built on Oct 15 2010 14:17:04) hits the bug.
- PHP 5.3.3-1ubuntu9.1 x86_64 build (built on Oct 15 2010 14:00:18) doesn't have the bug.
In the i686 build ltrace shows the memcpy call repeating infinitely, suggesting the bug originates from 32-bit and 64-bit problems.
Ubuntu 10.04, i686, "PHP 5.3.2-1ubuntu4.5 with Suhosin-Patch" has the bug.
Ubuntu 10.10, i686, "PHP 5.3.3-1ubuntu9.1 with Suhosin-Patch" has the bug.
Debian Lenny with 2.6.26-1-amd64 kernel, i686, "PHP 5.2.6-1+lenny9 with Suhosin-Patch" has the bug.
Debian Lenny with custom kernel build, x86_64, "PHP 5.3.3-5 with Suhosin-Patch" does not have the bug.
So the common thread seems to be 32- vs 64-bit: the bug occurs on all the 32-bit boxes I tested, but not on any of the 64-bit boxes.
I think that the vast majority of stuff doesn't qualify as something that should be reported to a security team first. However, from a brief glance at the article, it appears that a remote user of a web application may be able to crash PHP entirely if he can find the right place to drop the number, denying access to all programs that depend on that PHP installation. That is a very serious bug and one incident can theoretically take out hundreds or thousands of sites and cost a lot of people a lot of money, not to mention time or frustration. Definitely seems like it should have hit the security group first to me.
Perhaps the real issue is that the growing reliance upon internet services makes fault-tolerant engineering and fallback plans for handling failures very important. We need to make sure that hospitals/police/everything aren't dependent on systems that might break down completely because of bugs like this.
Yes, which is why you should carefully reduce the number of bugs in your code - many bugs can be security issues, if the attacker is clever enough.
do { z = (x & 0xffff) y + (xc >> 16) + carry; carry = z >> 16; Storeinc(xc, z, z2); z2 = (x++ >> 16) * y + (*xc & 0xffff) + carry; carry = z2 >> 16; } while(x < xae);
Hit up gdb and watch xae and x...
I'll try myself but I don't have 32bit.
Works fine with -O0 but not -O2
http://twitter.com/rasmus/statuses/22212610308964353(I haven't looked at this particular issue; maybe it really is a compiler bug. But the odds are against it.)
He also later added:
We still need to fix the code to make it immune to compiler switches.When you attempt to make an argument from authority, at least choose someone who has some credibility to use as an authority.
In other words, "We know that our code is buggy and it is relying on undefined behavior."
I.e., this developer knows it's a problem with his code, not the compiler, but he phrases it as if the "compiler switches" are the things causing the problems. This is classic developer arrogance and immaturity.
Thank God I don't rely on his software for anything important.
[1:26am:~/Downloads] RIDGELAND:root [0:16]# php -v
PHP 5.3.3 (cli) (built: Aug 22 2010 19:41:55)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies %php -r 'print(2.225073858502011e-308+0);print("\n");'
2.225073858502E-308
%uname -a
FreeBSD unknown 8.1-RELEASE FreeBSD 8.1-RELEASE #0: Mon Jul 19 02:36:49 UTC 2010 root@mason.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC amd64
%php -v
PHP 5.3.3 with Suhosin-Patch (cli) (built: Oct 17 2010 13:41:11)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
with Suhosin v0.9.32.1, Copyright (c) 2007-2010, by SektionEins GmbHhttp://gcc.gnu.org/ml/gcc/2003-08/msg01195.html is a good explanation.
strace php p.php
lstat64("/home/ubuntu/junk/p.php", {st_mode=S_IFREG|0644, st_size=59, ...}) = 0
lstat64("/home/ubuntu/junk", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/ubuntu", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
ioctl(3, SNDCTL_TMR_TIMEBASE or TCGETS, 0xbfe51238) = -1 ENOTTY (Inappropriate ioctl for device)
fstat64(3, {st_mode=S_IFREG|0644, st_size=59, ...}) = 0
mmap2(NULL, 68, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb78d1000
The next thing that should be hapenning is munmap for that very same address, but something hangs... S EE..E MM..M
0 00000 11111 $ php -v
PHP 5.2.6 (cli) (built: May 5 2008 10:32:59)
Copyright (c) 1997-2008 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies
with eAccelerator v0.9.5.3, Copyright (c) 2004-2006 eAccelerator, by eAccelerator
$ uname -a
Linux hostname 2.6.18-128.1.10.el5 #1 SMP Thu May 7 10:39:21 EDT 2009 i686 i686 i386 GNU/Linux mysite.com/page/1 ===> $page = 1 ===> href="/page/' . $page + 1 . '">next page
could mess you up...I am not affected by the bug, yet am on a 32 bit CPU and PHP 5.2.16 was compiled with -O2.
uname -a:
Linux www 2.6.9-67.0.22.ELsmp #1 SMP Fri Jul 11 10:38:12 EDT 2008 i686 i686 i386 GNU/Linux
Running the test script outlined above comes back immediately. No hang.
CPU is Intel(R) Xeon(R) CPU E5430 @ 2.66GHz which support SSE2.
Since my CPU supports SSE2, would I not be affected by this?
http://www.aircraft24.com/en/info/php-float-dos-quickfix.htm
Its a quick+dirty fix for site-owners that cannot immediately upgrade php.
ubuntu@ip-10-130-57-139:~$ php -v
PHP 5.3.3-1ubuntu9.1 with Suhosin-Patch (cli) (built: Oct 15 2010 14:00:18)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies PHP 5.3.2 (cli) (built: Aug 7 2010 00:04:41)
Copyright (c) 1997-2010 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
Mac / Snow Leopard PHP 5.3.3-1ubuntu9.1 with Suhosin-Patch (cli) (built: Oct 15 2010 14:17:04)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
The build time differs, for some reason. PHP 5.2.6-1+lenny9 with Suhosin-Patch 0.9.6.2 (cli) (built: Aug 4 2010 03:25:57)
Copyright (c) 1997-2008 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies aquilax@zelda /tmp> php -v
PHP Warning: PHP Startup: Unable to load dynamic library '/usr/lib/php5/20090626+lfs/adodb.so' - /usr/lib/php5/20090626+lfs/adodb.so: cannot open shared object file: No such file or directory in Unknown on line 0
PHP 5.3.3-6 with Suhosin-Patch (cli) (built: Dec 7 2010 18:23:49)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans
with Suhosin v0.9.32.1, Copyright (c) 2007-2010, by SektionEins GmbHPHP 5.3.1 (cli) (built: Nov 20 2009 17:26:32) Copyright (c) 1997-2009 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2009 Zend Technologies
glaceon:~ $ php -v PHP 5.3.3 (cli) (built: Aug 22 2010 19:41:55) Copyright (c) 1997-2010 The PHP Group Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
glaceon:~ $ uname -a Darwin glaceon 10.5.0 Darwin Kernel Version 10.5.0: Fri Nov 5 23:20:39 PDT 2010; root:xnu-1504.9.17~1/RELEASE_I386 i386 i386
PHP 5.3.2-1ubuntu4.5 with Suhosin-Patch (cli) (built: Sep 17 2010 13:41:55) booi@booi-desktop:~$ uname -a
Linux booi-desktop 2.6.32-27-generic #49-Ubuntu SMP Thu Dec 2 00:51:09 UTC 2010 x86_64 GNU/Linux
booi@booi-desktop:~$ php -a
Interactive shell
php > $a = 2.2250738585072011e-308;
php > print $a;
2.2250738585072E-308
php > print $a/2;
1.1125369292536E-308I wasn't referring to this being the cause, just in other languages if you pass a string through JSON it would never end up being decoded to a double, just because syntactically it is a double.
I can't see anything wrong with this behavior. Double isn't supposed to be any less safe than any other type.
> I wasn't referring to this being the cause, just in other languages if you pass a string through JSON it would never end up being decoded to a double, just because syntactically it is a double.
I believe there is a fundamental misunderstanding here. Other languages would indeed convert to double if so instructed, or if the value presented was a (high) floating point value. And once again, I can see no inherent problem with a JSON parser that looks at a floating point number and interprets it as a double. The only problem would be the memory space of a double vs that of a float or smaller type, but since PHP doesn't make that distinction the point is moot. I don't see the evilness of it, nor do I see how static typing would avoid bugs such as this.
More generally speaking, if one of the basic types of a language is defectively handled, there is no way this bug goes away if you declare that type beforehand. It has quite simply nothing to do with it. I guess an argument could be made that less code would be vulnerable on account of having less instances of doubles around, but it would still be a huge problem. And it's not like floating point numbers are somehow rarely used.