
PHP 5.3.3 hangs on numeric value 2.2250738585072011e-308 - anorwell
http://www.exploringbinary.com/php-hangs-on-numeric-value-2-2250738585072011e-308/
======
lifthrasiir
Follow-up: PROBLEM SOLVED.

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.

~~~
astrange
A better fix would be to use "-mfpmath=sse" to disable x87 math - this also
makes your program slightly faster, whereas -ffloat-store can make it much
slower.

~~~
lallysingh
.. or not use an algorithm that depends on the hardware implementation of
floating point math? How about just comparing the previous & current iteration
values? If you're not making any progress, you're done.

~~~
sausagefeet
You'd probably want to compare to an epsilon AND exact compare, otherwise you
could oscillate between two values.

------
jrockway
_If you have any thoughts on what the bug is, please let me (or PHP) know._

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.

~~~
RossM
>No offense, but this is like programming 101.

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).

~~~
JonnieCache
You can do all this in any decent scripting language too. In ruby:

    
    
        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 :)

------
thamer
As the author said, it does hang in zend_strtod.c, and it seems to happen in
32-bit only.

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:1192

~~~
ars
Here's the source for that file if you want to look at it:
[http://svn.php.net/viewvc/php/php-
src/branches/PHP_5_3/Zend/...](http://svn.php.net/viewvc/php/php-
src/branches/PHP_5_3/Zend/zend_strtod.c?revision=255174&view=markup&pathrev=260750)

------
texeltexel
Bug appearing at my Core 2 Duo / Win7 / PHP 5.3.0.

This 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?

~~~
bengtan
Ugh, I managed to hang one of my websites by sticking that number in place of
a normal integer parameter.

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.

~~~
trezor
So basically you can hang (almost) _any_ website written in PHP which expose
primary keys as GET parameters?

Does this just crash the local process for that request or will it also cause
problems for other requests? Anyone able to test this?

~~~
dangrossman
It's going to be a very, very small subset of websites running PHP that could
be hung...

\- 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

~~~
infinity
It seems to me that not only the PHP versions 5.3.3 are vulnerable. I have
successfully crashed an older version of PHP 5.0.3.

------
lifthrasiir
Some of my friends verified the case. Highlights:

\- 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.

~~~
RyanGWU82
My experience matches yours: the bug occurs in i686 but not x86_64. I have a
variety of boxes with different Linux distributions, and tried it on each.

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.

~~~
bobds
So it's also in the 5.2.* branch? That can't be good.

------
stephenjudkins
Though the author doesn't seem to be malicious in any way, he really should
have reported it to the PHP core team as a security vulnerability before
writing a blog post. This could easily lead to denial-of-service attacks.

~~~
mycroftiv
Aren't a huge percentage of software bugs potential security vulnerabilities
by this standard? I understand the wisdom of trying to get patches pushed out
before publicly disclosing major exploits in mission critical software, but it
seems unreasonable to expect users to not make statements of the form "program
X crashes when I do such-and-such" in public discourse.

~~~
cookiecaper
It just depends on how the crash is triggered. Most things that users run into
in normal usage don't have very much potential to become a serious attack
vector for lots of reasons -- many programs are not particularly network-aware
or may not provide any service an attacker is interested in denying, the
attack may require local access (which is still serious in some cases, but
obviously less than something that can be exploited over the network by
sending a malformed query), and so on.

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.

~~~
mycroftiv
Yes, I understand that bugs in the "platform software" used by many internet
services have the potential to rapidly affect a lot of users. However, I
believe that it is unreasonable to expect users who experience a crash in php,
or mysql, or any important application you can name, to not talk about the
crash that happened. Software bugs are just too common to impose that
restrictive a standard. In general, I believe statements of the form "this
information is too dangerous to be shared publicly" are suspect, and we should
maintain a strong presumption that as a general rule, it is OK to talk about
problems publicly. When it comes to security issues, there is a difference
between releasing deliberately crafted 0-day arbitrary remote code execution
techniques and just making a public report of a crash you experienced.

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.

------
cantprogram
This must be it then:

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.

------
istvanp
Rasmus Lerdorf just tweeted that it's a gcc optimizer issue:

    
    
      Works fine with -O0 but not -O2
    

<http://twitter.com/rasmus/statuses/22212610308964353>

~~~
gjm11
When a problem is described as an optimizer issue, it usually means that the
code is making some unsound assumptions that happen to be true when compiling
without optimization. Of course it's easier on the programmer's ego to blame
it on the compiler...

(I haven't looked at this particular issue; maybe it really _is_ a compiler
bug. But the odds are against it.)

~~~
istvanp
Might be true in general, but when the creator of PHP says it's a compiler
issue it's hard to 2nd guess.

He also later added:

    
    
      We still need to fix the code to make it immune to compiler switches.

~~~
SimHacker
"I'm not a real programmer. I throw together things until it works then I move
on. The real programmers will say "Yeah it works but you're leaking memory
everywhere. Perhaps we should fix that."" -Rasmus Lerdorf

When you attempt to make an argument from authority, at least choose someone
who has some credibility to use as an authority.

------
tptacek
Doesn't hang for me:

    
    
      [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

------
roel_v
Can everybody who posts php -v (in as far as that is necessary...) also post
uname -a, otherwise there's not much to go on...

------
calloc

        %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 GmbH

------
scottmac
I've fixed this now in all the PHP branches.

[http://svn.php.net/viewvc/?view=revision&revision=307095](http://svn.php.net/viewvc/?view=revision&revision=307095)

~~~
roel_v
Interesting. I've never used 'volatile', would you mind explaining how it
works in this case?

~~~
scottmac
The copying from the 80-bit register to the 64-bit memory cell is causing the
result to be corrupted. When we use volatile it tells the compiler not to
apply the optimisation which in this case was putting it in a register.

~~~
roel_v
But doesn't that make it a compiler bug? If the root cause is that the
compiler truncates data it shouldn't, does that mean the fundamental issue is
a compiler bug?

~~~
scottmac
It's a hardware issue with the x87 instruction set,

<http://gcc.gnu.org/ml/gcc/2003-08/msg01195.html> is a good explanation.

~~~
roel_v
Ah I see, thank you. Hairy problem.

------
yuvadam
Check out the last lines for

    
    
      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...

~~~
yuvadam
And if anyone wondered, this number in double precision IEEE 754 is:

    
    
      S EE..E MM..M
      0 00000 11111

~~~
nitrogen
So this is the largest positive denormalized number? Does PHP hang for all
denormalized numbers?

------
dovyski
I can't reproduce on CentOS:

    
    
      $ 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

------
Jach
Awesome, this catches HostGator's PHP 5.3.3 (which isn't used by default, have
to turn it on yourself) too. I knew there was yet another good reason for
always casting expected-int input before doing anything with them... Something
as simple as

    
    
        mysite.com/page/1 ===> $page = 1 ===> href="/page/' . $page + 1 . '">next page
    

could mess you up...

------
jflaxen
I am confused. Does this only affect people with older cpus that do not
support SSE2?

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?

------
yuvadam
And so, the race after affected websites starts...

------
aircraft24
We have slapped together a quick workaround that can be found here:

<http://www.aircraft24.com/en/info/php-float-dos-quickfix.htm>

Its a quick+dirty fix for site-owners that cannot immediately upgrade php.

------
mhansen
I can't reproduce.

    
    
        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

~~~
anorwell
It happens for me on nearly the exact same:

    
    
        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.

~~~
natrius
The build time is different because you're running on different architectures
with different packages.

------
thefox
I can't reproduce under Debian:

    
    
      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
It does hang on my debian:

    
    
      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 GmbH

------
wrijnders
Hangs with the php version installed with xampp 1.7.3 on Windows 7.

PHP 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

------
srslynao
glaceon:~ $ php -r 'print(2.225073858502011e-308+0);print("\n");'
2.225073858502E-308

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

------
christophe971
It does hang on my Ubuntu desktop:

    
    
      PHP 5.3.2-1ubuntu4.5 with Suhosin-Patch (cli) (built: Sep 17 2010 13:41:55)

------
motto
PHP Version 5.3.0 via a default WAMP install on Windows 7 hangs as well

------
bengtan
Ouch, this hangs if PHP is run from the command line on Lucid Lynx.

~~~
booi
Doesn't seem to hang on my desktop lucid install.

    
    
      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-308

------
robryan
Good advertisement in a way for more type safe languages, given I'm passing
something as a string into JSON then using it as a string but PHP still
converts it to a double which triggers this error.

~~~
Udo
This isn't really a bug that originates from PHP's handling of types. If any
floating point variable containing this number crashes the process, it doesn't
matter whether the type declaration was implicit or not. Also, since you're
using JSON as an example (why?), it's worth noting that type-safe languages
require some sort of mapping for JSON fields as well, any of which could be
FLOAT or DOUBLE and would thus be vulnerable. Until today there was no reason
to assume the floating point types weren't safe, either. Finally, it has
already been pointed out that the problem is caused by a GCC optimization bug,
so the implication is that any recent GCC-compiled executable may be
vulnerable to erratic behavior upon encountering this number.

~~~
robryan
I was using JSON as an example because it's taking something in JSON specified
as a string, using quotation marks, and automatically converting it to a
double.

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.

~~~
Udo
> _taking something in JSON specified as a string, using quotation marks, and
> automatically converting it to 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.

