Java Hangs When Converting 2.2250738585072012e-308

Konstantin Preißer made an interesting discovery, after reading my article “PHP Hangs On Numeric Value 2.2250738585072011e-308”: Java — both its runtime and compiler — go into an infinite loop when converting the decimal number 2.2250738585072012e-308 to double-precision binary floating-point. This number is supposed to convert to 0x1p-1022, which is DBL_MIN; instead, Java gets stuck, oscillating between 0x1p-1022 and 0x0.fffffffffffffp-1022, the largest subnormal double-precision floating-point number.

Send a Java Program Into An Infinite Loop

Compile this program and run it; the program will hang (at least it does on a 32-bit system with the latest JRE/JDK):

class runhang {
public static void main(String[] args) {
  System.out.println("Test:");
  double d = Double.parseDouble("2.2250738585072012e-308");
  System.out.println("Value: " + d);
 }
}

Send the Java Compiler Into An Infinite Loop

Try to compile this program; the compiler will hang:

class compilehang {
public static void main(String[] args) {
  double d = 2.2250738585072012e-308;
  System.out.println("Value: " + d);
 }
}

Where’s the Problem?

For the runtime case at least, Konstantin has narrowed the problem down to the “correction loop” in FloatingDecimal.java. See his comments on my PHP bug analysis article.

Like PHP, Java gets stuck crossing the normalized/unnormalized border, but in the opposite direction: it starts with an estimate just below DBL_MIN — 0x0.fffffffffffffp-1022 — and is trying to get up to DBL_MIN. but with a twist: it starts with an estimate that is correct — DBL_MIN — and then adjusts it to 0x0.fffffffffffffp-1022. It then adjusts that back to DBL_MIN, and around it goes…

Bug Report

Konstantin reported this problem to Oracle three weeks ago, but is still waiting for a reply. (Update: as per Konstantin’s comment below, the bug has been assigned “internal review ID of 1949967, which is NOT visible on the Sun Developer Network (SDN)”.)

Update: Previous Bug Reports Describe the Same Problem

Readers found two bug reports that describe the same problem (although not in terms of the magic number 2.2250738585072012e-308): bug number 100119 from 2009, and bug number 4421494 from 2001. (But don’t bother clicking on that last one — the link is now dead, as of 2/3/11.)

Addendum

As pointed out in the comments below, equivalent forms of the number cause the problem as well; examples:

  • 0.00022250738585072012e-304 (decimal point placement)
  • 00000000002.2250738585072012e-308 (leading zeros)
  • 2.225073858507201200000e-308 (trailing zeros)
  • 2.2250738585072012e-00308 (leading zeros in the exponent)
  • 2.2250738585072012997800001e-308 (superfluous digits beyond digit 17)

Update: Discussion on the Web

Check out these links for more discussion of the bug:

Update: A Fix Was Released

On 2/8/2011, Oracle released a fix.

Dingbat

133 comments

  1. Hi,

    thanks for publishing this article! 🙂

    Note that the ‘correction loop’ that I was referring to, is at lines 1473 – 1599 of the linked FloatingDecimal.java (marked wtih “correctionLoop”). If these lines are removed, the function does not hang any more, but the result for other numbers is less accurate than with that correction loop.

    This could also be used as a workaround, if you need to provide a user interface from a java application where the user can enter a string which is converted to a double, and it is critical that the application must not hang (e.g. when parsing the String in a Java Servlet, which is used just like PHP for creating dynamic websites), you can copy the FloatingDecimal.java into your project (change the “package sun.misc;” line), and remove the “correction loop”.
    Then you would need to replace “Double.parseDouble(…)” with “yourPackage.FloatingDecimal.readJavaFormatString(…).doubleValue()”.

    However, I think this bug is less critical than PHP’s bug, because Java Servlets are not used as much as PHP, and because of Java’s strong type system, you would have explicitly use “Double.parseDouble()” to parse a String containg a floating point number.

  2. The compiler hangs on a 64bit version as well.

    java version “1.6.0_22”
    Java(TM) SE Runtime Environment (build 1.6.0_22-b04)
    Java HotSpot(TM) 64-Bit Server VM (build 17.1-b03, mixed mode)

  3. Of course, it follows that scala has the same problem. If you start up the command line interpreter (scala w/o any arguments) and ask it to evaluate the value under discussion, it will hang, hogging a CPU. I assume the same will go for compiled programs as well.

    cheers

  4. Works on Google documents too! (Spreadsheet)

    Enter this double value into a cell (you may or may not have to force the back-end to evaluate the value by inserting something like “=+2” into a different cell).

    The Ajax back-end hangs until it times out.

  5. My greater-and-less-than signs disappeared in my last comment..

    …you may or may not have to force the back-end to evaluate the value by inserting something like “=cell-with-this-double-constant+2″ into a different cell (e.g. =B2+2)…

  6. This is an awesome find! Way to go! Waiting for news about hackers abusing this bug around the world for financial gain

  7. Uh… use BigDecimal instead? Double and Floats are known to be buggy and have problems with some operations (like division). BigDecimal was created to address those problems.

  8. Hello,
    @g0dkar:

    Float and Double are used for fast floating point arithmetics, and they are in general not buggy (except this bug here). It is just the nature of floating point numbers. You should not just use BigDecimal instead of Double, because BigDecimal is intended for use when you need to do calulations which are more accurate than Doubles.

    However BigDecimal is MUCH slower than double, because it doesn’t use floating point arithmetics, but does the calculations manually (for example, a calulation which takes 0,017 ms with doubles, took 25780,43 ms with BigDecimal).
    So you should not generally replace double with BigDecimals. As long as you don’t need a better precision than Double, you should use Double.

  9. @Maciej,

    Yes, equivalent forms of the number work as well:

    – 0.00022250738585072012e-304 (decimal point placement)
    – 00000000002.2250738585072012e-308 (leading zeros)
    – 2.225073858507201200000e-308 (trailing zeros)
    – 2.2250738585072012e-00308 (leading zeros in the exponent)
    – 2.2250738585072012997800001e-308 (superfluous digits beyond digit 17)

    (I added this to the article.)

  10. It works in Haskell’s GHCI Version 6.10.4.

    Typing 2.2250738585072012e-308 at the REPL yields 2.2250738585072014e-308

  11. Hi,
    When I submitted that bug, I got following E-Mail:

    Your report has been assigned an internal review ID of 1949967, which is NOT visible on the Sun Developer Network (SDN).

    Please be aware that the large volume of reports we receive sometimes prevents us from responding individually to each message.

    If the information is determined to be a new Bug or RFE, or a duplicate of a known Bug or RFE, you will receive a followup email containing a seven digit bug number.

    But so far, I have not received another response about that report.

  12. @Michael,

    2.2250738585072014e-308 is the “canonical” form of DBL_MIN, but all five of these 17 digit numbers map to DBL_MIN:

    – 2.2250738585072012e-308
    – 2.2250738585072013e-308
    – 2.2250738585072014e-308
    – 2.2250738585072015e-308
    – 2.2250738585072016e-308

    2.2250738585072014e-308 is probably chosen because it is “safely” in the middle (and round-trips to boot).

  13. Seems to be passing on .NET. Tried with Visual Studio 2010 C# compiler, Debug & Release targets, .NET frameworks 2.0-4.0.

  14. Note that runhang and compilehang work perfectly(?) in the Java 1.5.0 implementation of the GNU Compiler Collection (tested with gij/gcj 4.4.5) on a 32-bit system.

    It also works with 2.2250738585072011e-308.

    However, the output is 2.225073858507201E-308 using both numbers.

  15. I’m less than impressed but not at all surprised that Oracle doesn’t take this seriously. What, the PHP bug was fixed in 3 days, Oracle hasn’t even responded in 3 weeks! Jeez,the JVM is certainly in the right hands! I can’t wait until the first hacker creates a crawler that posts this number to forms, rest-apis and attackable surfaces all over the net, wreaking all kinds of havoc. My bet is that this will happen in the next couple of days/weeks. In my experience, lots of developers do string=>primitive conversion before they do form validation, so I believe the impact of this could potentially be huge. Even if/when Oracle eventually get around to roll out a fix that works, it will still take ages until the patch is adopted by slow moving corporations. I’m surprised I haven’t read about this in any big IT newspaper yet.

  16. @Eirik,

    Just to set the record straight: the PHP bug wasn’t taken seriously until I publicized it. That said, they fixed it in about a day — so kudos to them.

  17. By the way, there is another Bug in Sun’s Java 6 (at least under Windows) which crashes the VM. Try this:

    public static void main(String[] args) {
    BufferedImage rendImg = new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR);
    BufferedImage drawImg = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = drawImg.createGraphics();
    g.transform(new AffineTransform(1E-16, 0.0, -1.0, 1.0, 0.0, 0.0));
    g.drawImage(rendImg, -rendImg.getWidth() / 2, -rendImg.getHeight() / 2, null);
    }

    Result:

    #
    # A fatal error has been detected by the Java Runtime Environment:
    #
    # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x000000006d03ed69, pid=3652, tid=1548
    #
    # JRE version: 6.0_23-b05
    # Java VM: Java HotSpot(TM) 64-Bit Server VM (19.0-b09 mixed mode windows-amd64 compressed oops)
    # Problematic frame:
    # C [awt.dll+0x3ed69]
    #
    # An error report file with more information is saved as:
    # C:\Java-Sachen\eclipse-workspace-Test\JVMCrash\hs_err_pid3652.log
    #
    # If you would like to submit a bug report, please visit:
    # http://java.sun.com/webapps/bugreport/crash.jsp
    # The crash happened outside the Java Virtual Machine in native code.
    # See problematic frame for where to report the bug.
    #

    I also submitted that bug to oracle some months ago (for Java 1.6.0_22), but also didn’t get any response on this. 😀

  18. interestingly it seems to be possible to crash almost any server running a servlet engine by passing this value inside the http header (in Accept-Language as numeric value) see [RR: dead link removed] for a short explanation 🙂

  19. Note that for the crash code above, you need the correct imports, i.e.

    import java.awt.Graphics2D;
    import java.awt.geom.AffineTransform;
    import java.awt.image.BufferedImage;

    public class MyCrashTest {
    public static void main(String[] args) {
    BufferedImage rendImg = new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR);
    BufferedImage drawImg = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = drawImg.createGraphics();
    g.transform(new AffineTransform(1E-16, 0.0, -1.0, 1.0, 0.0, 0.0));
    g.drawImage(rendImg, -rendImg.getWidth() / 2, -rendImg.getHeight() / 2, null);
    }
    }

  20. @Witty

    thats interesting but I was not able to crash tomcat using your code(tried 6.0.20 and 7.0.6) under 32 bit java jdk1.6.0_12 on Win 7 64-bit

  21. This is a really, really big deal.

    Most Java web application frameworks use tools like apache BeanUtils to automatically populate typed object fields from string http parameters. Any application that populates a double or Double from a form is now susceptible to a serious denial-of-service attack. REST frameworks are probably equally vulnerable.

    It wouldn’t be hard to shut down rather large chunks of the (java-powered) internet with this. How many appservers are smart enough to terminate cpu-bound threads? Not the common ones. This will require operator intervention.

    All you’d need is a script that crawls the net looking for forms, and posts that # for every field. Yikes.

  22. Both compile and runtime problems one can see using GroovyConsole. Not sure is this good or bad thing. At least it is consistent with Java.

  23. Guess what! I received following email from Oracle:

    Hi Konstantin,

    We have added this bug to our bug tracking system with the ID of: 7016457 As this may be determined that this could be an exploit, it will not appear on bugs.sun.com.

    Regards,
    Roger

  24. That seems correct.
    If you enter a Value between A:= 1*2^(-1022) – 0.5*2^(-52)*2^(-1022) and B:= 1*2^(-1022), it hangs.
    A ~= 2.225073858507201136057409796709131975934 * 10 ^ (-308);
    b ~= 2.225073858507201383090232717332404064219 * 10 ^ (-308);

    For example, System.out.println(Double.parseDouble(“2.225073858507201136057409796709131975934E-308”));
    works, but
    System.out.println(Double.parseDouble(“2.225073858507201136057409796709131975935E-308”));
    hans.

  25. Hmm, although in that bug report it says, the upper boundary is 2^(-1022) (Double.MIN_NORMAL), that’s not quite correct.
    It seems the upper bound is
    B = 2^(-1022) – 2^(-1076)
    ~= 2.225073858507201259573821257020768020077 * 10 ^ (-308)

  26. Hello,

    I am Roger Lewis from the Java SE team at Oracle. We would like to thank Konstantin for reporting these bugs and the concise reports. The posting by Rick and the comments by others on this thread are also much appreciated. We have our development teams investigating these bugs.

    Thank you again,
    Roger

  27. Rick, saying that we didn’t take it seriously until you publicized is more coincidental than systematic, I think. You reported the bug on Thursday Dec.30. The holidays and New Year’s meant fewer eyes on the bug reports and you “publicized” it on Monday Jan.3 when at least some of the core folks were just sobering up and getting back online. At that point we fixed it quickly. It was about a week from bug report to a new release.

    We also get a lot of completely bogus bug reports. It takes a while to sort through the chaff. For future reference, for any bugs that seem security-related, email security@php.net.

  28. Hi Rasmus,

    Point taken about the holidays. I’m willing to believe it was just bad timing (maybe I should have been partying and not messing around with subnormal numbers :)). From my point of view though it was such a simple bug to reproduce (one line of code) that I expected at least a response to the effect of “we can/can’t reproduce this.” But like you said — and like I already acknowledged — you took care of it quickly once “more eyes” were on it.

    In any case, I’ll expect MY bug reports to be fast-tracked in the future 🙂 .

    | BTW, have you seen my article analyzing the bug?

  29. Hi,

    the bug report on OpenJDK suggests to change in FloatingDecimal.java inside the ‘correction loop’:
    – if ( (bigIntNBits == 1) && (bigIntExp > -expBias) ){
    + if ( (bigIntNBits == 1) && (bigIntExp > -expBias+1) ){

    If I change this line in the FloatingPoint.java and then try

    System.out.println(myPackage.FloatingDecimal.readJavaFormatString(“2.2250738585072012e-308”).doubleValue());

    it does not hang any more, and correctly prints “2.2250738585072014E-308” as result. So I guess that change fixes the bug 😉

  30. Hi,

    for the record. Same with Java for security related bugs. There is a special Emailaddres for reporting security vulnerabilities: secalert_us at oracle.com.

    Thanks,
    Markus

  31. Rick, I did read your analysis, yes. You spend way too much time pondering this stuff.

    As for being simple to reproduce. Personally I had to go find a 32-bit vm image before I could reproduce it, so it was more than just a 1-line test script’s worth of effort. PHP bugs also go through a couple of levels of review. An initial quick bogus filter to weed out the ridiculous or incomplete ones. Since your bug wasn’t immediately marked bogus, you passed that. Then typically a closer look by someone more experienced, in this case Pierre, who was able to reproduce it and poked Dmitry for his take on it since he knows that part of the code best. Dmitry was unfortunately away skiing somewhere in Siberia, or somewhere equally remote which slowed things down by another day. I think it was then Derick who figured out it went away with -O0 which is always a bad sign and we zeroed in on it from there. I was visiting Facebook and sat down with Scott there and we tested a couple of different fixes and we determined just marking it volatile was the safest short-term fix for now.

    So, that was the full story of the fix.

    I would be interested in seeing the equivalent story for the 2009-reported Java version of this. 🙂

  32. @Roger Lewis

    Java 5 is at end of life. Will an eventual bug fix be released for this platform as well?

    While we’re waiting for the fix, feel free to play around with this little curl-invocation:

    curl -H “Accept-Language: en-us;q=2.2250738585072012e-308” http://url

    Any Tomcat-server calling getLocale() on the request (i.e most of them, especially those with Spring) will hang the processing thread. Not sure about jetty, oc4j etc but I guess they have similar weaknesses.

  33. Hello,
    @Erik

    I am using Tomcat 7.0.6 for hosting Java Servlets on my Websites, and I tried to send this HTTP request:

    GET / HTTP/1.1
    Host: myhost
    Connection: keep-alive
    Accept-Language: en-us;q=2.2250738585072012e-308

    However it doesn’t seem to have any effect. Tomcat processes the request normally and doesn’t hang. (I don’t use any getLocale() calls, may this be the reason?)

  34. @Rasmus,

    Yes, I DO spend way too much time pondering this stuff. But c’mon: double rounding on underflow — how cool is that? You don’t see THAT everyday!

    Thanks for the full backstory. The only thing I’ll still say is that since it was reproducible, at least that much should have been stated in the initial bug report response (and perhaps it should have been marked as a security issue at that point?).

    In any case, it doesn’t seem any harm has come to the Web from this, and I learned a ton from writing the articles.

    Thanks for reading.

  35. @Konstantin
    Try calling request.getLocale() in one of your servlets. If your goal is just to check it out, you can also try it on Tomcats helloworld-servlet that is bundled in the release – it calls request.getLocale.

    The actual trouble occurs in org.apache.catalina.connector.Request.parseLocalesHeader where this code is run:

    // Extract the quality factor for this entry
    double quality = 1.0;
    int semi = entry.indexOf(“;q=”);
    if (semi >= 0) {
    try {
    quality = Double.parseDouble(entry.substring(semi + 3));
    } catch (NumberFormatException e) {
    quality = 0.0;
    }
    entry = entry.substring(0, semi);
    }

  36. I added this fix to FloatingDecimal.java

    1475: int iterationCount = 100;

    1477: while (–iterationCount > 0) {

    Inserted after 1599:
    if (iterationCount == 0) {
    throw new NumberFormatException(“SPIN LOOP DETECTED – TRAPPING EXPLOIT. Value = ” + this.toString());
    }

    At least this way, you will be able to detect anyone using the exploit.

    I’m sure you can loop fewer than 100 times, but I did that just to be safe as the code in that loop is hairy.

    Obviously, you’ll need to do bad things with rt.jar and replace sun.misc.FloatingDecimal.class with this one, which is against the license agreement… so this is for information purposes only!

  37. Since this affects JRuby, I decided to find a workaround that doesn’t involve tweaking rt.jar-based classes. I blogged my solution here: http://blog.headius.com/2011/02/working-around-java-doubleparsedouble.html

    It’s not particularly elegant but it works. I essentially just use BigDecimal to parse the mantissa, and then proceed from there back to a primitive double. I believe it solves the cases in question, but I’d love to have feedback on ways to improve it (on my blog…I’m not monitoring this post for comments).

  38. Further to my previous post #59 – You don’t specifically need to fiddle with rt.jar.

    You can of course put FloatingDecimal in it’s own jar and run java with

    -Xbootclasspath/p:

    to prepend the new FloatingDecimal to the boot clas path.

    Either way works 🙂

    Also, I’ve ran Double.parseDouble nearly a billion times with random values to parse, and the maximum iterations encountered with the set I’ve tested is 3.

    100 is definately overkill, but I’m still very nervous about reducing it to 3!

  39. The BugId 4421494 mentioned before, was removed from the Bug Database. Probably due security and shame reasons 😉

    Couldn’t found a cached version trough google.

  40. This was the content of the bug report:

    Bug ID: 4421494
    Votes 1
    Synopsis infinite loop while parsing double literal
    Category java:classes_lang
    Reported Against 1.3 , 1.4.1
    Release Fixed
    State 5-Cause Known, bug
    Priority: 4-Low
    Related Bugs 4396272 , 4749698 , 4887667 , 6876342
    Submit Date 04-MAR-2001
    Description

    java version “1.3.0”
    Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C)
    Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode)

    Parsing double literals very near the exact halfway point of the cutoff for
    denormalized numbers has some problems. This cutoff can be determined as the
    average of Double.longBitsToDouble(0x0010000000000000L) and
    Double.longBitsToDouble(0x000fffffffffffffL), using the precise math of
    java.math.BigInteger. The halfway point must round up, according to IEEE 754
    round-to-nearest rules.

    With this example, the literal is larger than the lines – be sure that there are
    no line breaks in the actual number when compiling.

    Basically, appending a non-zero extension at the end of the literal
    causes java.lang.Double.parseDouble to enter an infinite loop. As javac
    uses this library call, it is impossible to compile this example:

    class Literal {
    public static void main(String[] args) {
    System.out.println(
    0.000000000000000000000000000000000000000000000000000000000000000000000000000000
    00000000000000000000000000000000000000000000000000000000000000000000000000000000
    00000000000000000000000000000000000000000000000000000000000000000000000000000000
    00000000000000000000000000000000000000000000000000000000000000000000022250738585
    07201136057409796709131975934819546351645648023426109724822222021076945516529523
    90813508791414915891303962110687008643869459464552765720740782062174337998814106
    32673292535522868813721490129811224514518898490572223072852551331557550159143974
    76397983411801999323962548289017107081850690630666655994938275772572015763062690
    66333264756530000924588831643303777979186961204949739037782970490505108060994073
    02629371289589500035837999672072543043602840788957717961509455167482434710307026
    09144621572289880258182545180325707018860872113128079512233426288368622321503775
    66662250398253433597456888442390026549819838548794829220689472168983109969836584
    68140228542433306603398508864458040010349339704275671864433837704860378616227717
    38545623065874679014086723327636718751
    );
    }
    }

    However, remove the final 1, and the class compiles just fine. Unfortunately,
    it gives the output of 2.225073858507201E-308, intead of the correct
    2.225073858507201E-308, meaning that it rounded down instead of up.
    (Review ID: 118067)
    ======================================================================
    xxxxx@xxxxx 2004-11-11 21:44:14 GMT

    Work Around

    Don’t use either double in question (the exact halfway point or something near
    it). Instead, use a shorter literal that is closer to the desired double
    value, rather than abusing round-to-nearest rules.
    ======================================================================

    Evaluation

    Verified problem still exists in Merlin FCS; should be fixed.

    xxxxx@xxxxx 2002-05-28

    Decomitting from Tiger, problem should still be fixed in a future release.

    xxxxx@xxxxx 2003-09-08

    Submitted On 05-MAR-2001
    eblake

    Oops – I just caught a typo in my report. The correct value
    of the halfway point should be 2.2250738585072014E-308 (I
    left out the final 4).

    Submitted On 14-JUL-2004
    nadezhin_

    The problem can be fixed by changing a line “java.lang.FloatingDecimal.java:1514” in J2SE 1.4.2
    from:
    if ( (bigIntNBits == 1) && (bigIntExp > -expBias) ){
    to:
    if ( (bigIntNBits == 1) && (bigIntExp > -expBias+1) ){
    .

    PLEASE NOTE: JDK6 is formerly known as Project Mustang

  41. Tomcat has committed changes to parseLocalesHeader() late in the night of February 1st, 2011 that fix the Accept-Language issue. The HTTP/1.1 spec says the q value should have 3 digits after the period maximum, so they implemented that.

    The fix is in the recently released Tomcat 6.0.32, in the soon-to-be-released Tomcat 7.0.7, and will be in the next 5.5.x release. It is not in Tomcat 5.5.32, which was released a mere couple of hours before the fix went in.

  42. Hi
    It is hanging only for “2.2250738585072012e-308”, for other numbers say “2.2250738585072011e-308” or “2.2250738585072013e-308” and so on, working fine (not hanging)….

  43. Hi,
    @prabhu

    It is not only hanging for 2.2250738585072012e-308.

    It is hanging for any number between
    2^(-1022) – 2^(-1075) and
    2^(-1022) – 2^(-1076).
    For example, 2.22507385850720117e-308

  44. @phraktle,

    I tried FLT_MIN (1.175494351e-38 = 2-126) and a couple of values around it but there was no problem. The code in floatValue() is a little different than doubleValue(); for one, there is no correction loop. This is probably because it has enough intermediate precision (double-precision) to compute it accurately.

  45. @Konstantin, @g0dkar:

    I’m with g0dkar on this one.

    Been doing Java since 1.0.2 and have yet to come up with a good reason to NOT use BigDecimal. Long ago learned to avoid doubles and floats in Java at ALL costs.

    This works:
    BigDecimal d = new BigDecimal(“2.2250738585072012e-308”);

    If your app is SO performance sensitive that you need double/float rather than BigDecimal, consider Fortran.

  46. Hello,
    @intelectual_tortoise
    I would agree with you that if you need to to scientifical calculations which require a much higher precision (a mantissa with more than 52 bit), you should use BigDecimal than double.

    But besides that, double can be about 1516370 % faster than BigDecimal (if you do calculations which causes a growth of the mantissa, which is normally what you’re doing). If you do not require much higher precision than double, there really is no reason to use BigDecimal instead of double. Why would you like to avoid doubles and use extremely slow BigDecimal for your calculations? (You are right that you should avoid floats, as they are very less accurate, and only use them if you need to save memory).
    Especially if you need to program algorithms that take a lot of calculations with floating point numbers, it’s not advisable to use BigDecimal instead of double.

    Note that BigDecimal also requires much more memory than double, because every time it’s value changes, a new BigDecimal object needs to be created, whereas a double always takes 8 bytes, and is hardware-implemented by the underying processor.

    Please also note that since Java 1.0.2, a lot of improvements have been made to the JVM (HotSpot), for example JIT which makes Java applications nearly as fast as C(++) or other language which compile to native code.

  47. Hmm, it’s actually not 1516370 %, but 151649488 % (I was referring to my comment above, where I made a comparison of double and BigDecimal, where double took 0,017 ms and BigDecimal 25780,43 ms.)

  48. “compilehang” hangs on IBM JREs for AIX 5.3, both 64 bits and 32 bits (Java 6 SR6 pap6460sr6-20090925_01 and pap3260sr6-20090925_01).
    I have not tested all JREs on my system, but I guess Java5 and Java14 will fail too.

  49. @Konstantin
    My perspective is from mostly business-oriented applications. Any performance hit associated with BigDecimal is more than made up for by not having to mop up after float/double rounding issues.

    The mantissas are relatively small, the calculations are to fixed decimal places, etc.

    Memory’s not really an issue with modern GC: calculations are usually done in a fixed scope, objects go out of scope and are GC’ed.

    If an application domain requires so many float/double calculations with large mantissas that performance becomes an issue, it begs the question whether or not Java is the right tool.

  50. @seb

    Not sure I’m following. I’m not saying change the language. Neither am I saying don’t fix the bug. I’m just stating that I can’t imagine too many reasons to use the float/double aspect of the language in the problem domains for which the language is naturally suited.

  51. Unless there are transitive calls to parseDouble() from other Java internal methods in rt.jar (there are, but harder to exploit directly), one can use aspectj and load-time weaving to safeguard parseDouble calls as in:

    package hotfixes;

    public aspect ParseDoubleHotFix
    {
    double around(String s):
    !within(hotfixes.**) &&
    call(double java.lang.Double.parseDouble(String)) &&
    args(s)
    {
    if (s.indexOf(“2250738585072012”) >= 0) {
    throw new IllegalArgumentException(
    “We apologize for inconvenience, but this number is temporarily not” +
    ” parseable by Oracle: ” + s);
    } else {
    return Double.parseDouble(s);
    }
    }
    }

    And then running java with: java -javaagent:…/aspectjweaver*.jar …

    Works like a charm for Tomcat, for example (before they applied the Accept-Language patch).

  52. @Dawid Weiss

    A nice idea, dawid. You’re right, there are a number of Java classes which in fact do use Double.parseDouble inside the JVM. I’ve scanned the runtime source for 1.6.0_22 and found the following places which use Double.parseDouble:

    C:\jvm-bug\1.6.0_22\jdk\deploy\make\macosx\javaws\Globals.java (1 hits)
    Line 336: f.setDouble(null, Double.parseDouble(value));
    C:\jvm-bug\1.6.0_22\jdk\deploy\src\javaws\share\classes\com\sun\javaws\Globals_pre.java (1 hits)
    Line 334: f.setDouble(null, Double.parseDouble(value));
    C:\jvm-bug\1.6.0_22\jdk\hotspot\src\share\tools\LogCompilation\src\com\sun\hotspot\tools\compiler\LogParser.java (7 hits)
    Line 269: Double.parseDouble(search(atts, “stamp”)),
    Line 275: p.setEnd(Double.parseDouble(search(atts, “stamp”)));
    Line 279: compile.setStart(Double.parseDouble(search(atts, “stamp”)));
    Line 338: compile.setEnd(Double.parseDouble(search(atts, “stamp”)));
    Line 346: LogEvent e = new MakeNotEntrantEvent(Double.parseDouble(search(atts, “stamp”)), id,
    Line 353: currentTrap = new UncommonTrapEvent(Double.parseDouble(search(atts, “stamp”)),
    Line 372: NMethod nm = new NMethod(Double.parseDouble(search(atts, “stamp”)),
    C:\jvm-bug\1.6.0_22\jdk\j2se\make\tools\swing-nimbus\classes\org\jdesktop\synthdesigner\synthmodel\jibxhelpers\UIPropertyMapper.java (1 hits)
    Line 105: value = Double.parseDouble(ctx.attributeText(null, VALUE_NAME, null));
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\java\util\jar\pack\Histogram.java (1 hits)
    Line 729: limit = (int)( limit * Double.parseDouble(av[2]) );
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\org\apache\xalan\internal\xsltc\compiler\Template.java (1 hits)
    Line 217: _priority = Double.parseDouble(priority);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\org\apache\xalan\internal\xsltc\runtime\BasisLibrary.java (1 hits)
    Line 107: result += Double.parseDouble(dom.getStringValueX(node));
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\org\apache\xerces\internal\impl\dv\xs\AbstractDateTimeDV.java (1 hits)
    Line 807: return Double.parseDouble(buffer.substring(start, end));
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\org\apache\xerces\internal\impl\dv\xs\DoubleDV.java (1 hits)
    Line 83: value = Double.parseDouble(s);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\org\apache\xerces\internal\impl\dv\xs\DurationDV.java (1 hits)
    Line 341: return Double.parseDouble(buffer.substring(start, end));
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\org\apache\xpath\internal\objects\XString.java (1 hits)
    Line 133: result = Double.parseDouble(s.toString());
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\rowset\internal\XmlReaderContentHandler.java (1 hits)
    Line 990: return Double.parseDouble(s);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\tools\internal\xjc\model\CBuiltinLeafInfo.java (1 hits)
    Line 252: return JExpr.lit(DatatypeConverterImpl._parseDouble(lexical.value));
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\tools\internal\xjc\reader\dtd\bindinfo\BIUserConversion.java (1 hits)
    Line 83: add( m, new BIUserConversion( bi, parse(“”)));
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\xml\internal\bind\DatatypeConverterImpl.java (4 hits)
    Line 219: public double parseDouble(String lexicalXSDDouble) {
    Line 220: return _parseDouble(lexicalXSDDouble);
    Line 223: public static double _parseDouble( CharSequence _val ) {
    Line 237: return Double.parseDouble(val);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\xml\internal\bind\v2\model\impl\RuntimeBuiltinLeafInfoImpl.java (1 hits)
    Line 692: return DatatypeConverterImpl._parseDouble(text);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\xml\internal\bind\v2\runtime\reflect\opt\TransducedAccessor_field_Double.java (1 hits)
    Line 48: ((Bean)o).f_double=DatatypeConverterImpl._parseDouble(lexical);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\com\sun\xml\internal\bind\v2\runtime\reflect\opt\TransducedAccessor_method_Double.java (1 hits)
    Line 48: ((Bean)o).set_double(DatatypeConverterImpl._parseDouble(lexical));
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\java\lang\Double.java (1 hits)
    Line 509: public static double parseDouble(String s) throws NumberFormatException {
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\java\math\BigDecimal.java (1 hits)
    Line 3129: return Double.parseDouble(this.toString());
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\java\math\BigInteger.java (1 hits)
    Line 2765: return Double.parseDouble(this.toString());
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\java\text\DigitList.java (1 hits)
    Line 151: return Double.parseDouble(temp.toString());
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\java\util\prefs\AbstractPreferences.java (2 hits)
    Line 574: * {@link Double#parseDouble(String)}. If the attempt succeeds, the return
    Line 594: result = Double.parseDouble(value);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\java\util\prefs\Preferences.java (3 hits)
    Line 758: * integer as by {@link Double#parseDouble(String)}. Returns the specified
    Line 760: * is inaccessible, or if Double.parseDouble(String) would throw a
    Line 766: * with Double.parseDouble, this double is returned in preference
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\java\util\Scanner.java (6 hits)
    Line 2341: typeCache = Double.valueOf(Double.parseDouble(s));
    Line 2364: * {@link Double#parseDouble Double.parseDouble}. If the token matches
    Line 2364: * {@link Double#parseDouble Double.parseDouble}. If the token matches
    Line 2366: * is passed to {@link Double#parseDouble(String) Double.parseDouble} as
    Line 2366: * is passed to {@link Double#parseDouble(String) Double.parseDouble} as
    Line 2387: return Double.parseDouble(processFloatToken(next(floatPattern())));
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\javax\xml\bind\DatatypeConverter.java (2 hits)
    Line 217: public static double parseDouble( String lexicalXSDDouble ) {
    Line 218: return theConverter.parseDouble( lexicalXSDDouble );
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\javax\xml\bind\DatatypeConverterImpl.java (4 hits)
    Line 191: public double parseDouble(String lexicalXSDDouble) {
    Line 192: return _parseDouble(lexicalXSDDouble);
    Line 195: public static double _parseDouble( CharSequence _val ) {
    Line 209: return Double.parseDouble(val);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\javax\xml\bind\DatatypeConverterInterface.java (1 hits)
    Line 158: public double parseDouble( String lexicalXSDDouble );
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\classes\sun\reflect\annotation\AnnotationParser.java (2 hits)
    Line 446: return parseDoubleArray(length, buf, constPool);
    Line 509: private static Object parseDoubleArray(int length,
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\demo\java2d\J2DBench\src\j2dbench\report\HTMLSeriesReporter.java (2 hits)
    Line 58: lval = Double.parseDouble((String)lhs);
    Line 64: rval = Double.parseDouble((String)rhs);
    C:\jvm-bug\1.6.0_22\jdk\j2se\src\share\demo\jfc\Java2D\java2d\demos\Transforms\Rotate.java (1 hits)
    Line 131: demo.increment = Double.parseDouble(tf1.getText().trim());

    So, in reality, your aspect doesn’t quite catch everything – there are quite a few other bits you’d need to weave around. I think fundamentally you’d be much better off prepending a patched FloatingDouble to the bootstrap classpath and you can be damn sure you’ll be protected.

  53. @Gary

    Sure, you’re right — prepending bootclasspath is the right way to go. I just thought the AspectJ solution is an elegant approach to the problem (in fact, to be complete you’d need to catch Double.valueOf() and Method.invoke() too).

    Even so, when you do the weaving, it’s nice to see in the weaver log where all these double.parsedouble calls are (as in the Tomcat example).

  54. @Dawid Weiss: The method you wanted to safeguard was probably sun.misc.FloatingDecimal#doubleValue() which is where the error really is/was.

  55. I just reproduced with JRockit 1.6.0_22 on Windows.
    I also confirmed that this does not hang:
    new BigDecimal(“2.2250738585072012e-308”)

  56. @boa13

    Oh, this would be too simple… I was talking about load-time weaving and because aspectj weaver is written in Java, FloatingDecimal is loaded way before anything else is executed at the weaver level. So you can’t weave FloatingDecimal directly (with load-time weaving).

    Also, like I said — this was just an idea for a nice and elegant solution; for production I’d prepend bootclasspath with the patched FloatingDecimal.

  57. I just confirmed the issue on this JVM:
    $ java -version
    java version “1.4.2”
    Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2)
    Classic VM (build 1.4.2, J2RE 1.4.2 IBM AIX build ca142ifx-20100215 (SR13 FP4) (JIT enabled: jitc))

    Notably, this version of Java is what runs WebSphere Commerce, which is what powers the ecommerce sites of some of the biggest retails in the US (if not the world), who I will not name, and hope no other commenter names. Using a few curl invocations, someone could ruins the days of quite a few fortune 500’s.

    I’m currently emailing as many of these sites as I can, and trying to reach IBM. I suggest others do the same.

  58. I’m a system admin for an application, runs on WebLogic. WLS shipped with java 1.6.0_05, the app shipped with 1.5.0_06. It’s a pretty big deal, end-users all over the org use it 24/7, some outside vendors connect, considered one of our core applications.

    How big a deal is this bug? If it were your app would you pull out the stops to patch? Wait until a schedule maintenance period? Ignore it?

  59. @#103:

    The blog post said:

    In the spirit of trying to be open about how we approach the responsibilities associated with being the steward of Java, I thought it would be useful to share some of the “behind the scenes” activity involving the Security Alert that we released yesterday.

    Amusingly, in the spirit of openness, the blog post now appears to have been censored.

    Still available here though: http://www.oracle.cc/2011/02/double-trouble-how-a-java-security-issue-was-fixed/

  60. @Brian Dunbar:

    My client, who runs one of the biggest e-commerce site in my country, has dealt with it as a major high-priority must-fix issue. They had some floating-point input fields, and were running Tomcat. They had quite a few people work on that as soon as they heard about it, and fixed it in the next 24 hours.

    Anybody on the Internet could have taken the whole site down with a few hundred poisoned requests, which is trivial to do. I believe this was a low-enough volume that this would also have been under the radar of their anti-DDoS measures.

    @Grohl:

    If no code on the server calls getLocale() on the request object, then Tomcat doesn’t die.

    Most frameworks call getLocale() as part of their i18n features. The admin webapps that ship with Tomcat call getLocale() too.

  61. @Brian Dunbar

    I fully agree with boa13.
    Today my team spent quite a bit of time patching and double-checking (wow, that expression makes sense these days!) more than 100 servers which we run as a part of our SaaS service. Kudos to Oracle, the patch works just fine.

    I just tested (only one request per shop, will cause no harm) on three different European ecommerce sites, all major brands, running on a well known ecommerce SaaS platform. They all could be ddosed with very little effort. If you think about the potential damage caused to an ecommerce site by a mere set of some thousand requests, this is an absolute prio 1 issue.

  62. Actually the HTTP specs are clear on this: there shouldn’t be more than three digits after the dot. So Tomcat is at least partly faulty too here: there’s no reason to use floating-point numbers to parse a decimal value that has at most three digits after the decimal (it’s a bit as noobish as using floating-point numbers for monetary computations).

  63. @nono:

    You are right, and Tomcat recognized their fault as soon as they heard about the problem. They fixed their implementation the day after this blog was published.

    Releases including the fix have been made on the three Tomcat branches: Tomcat 5.5.33, Tomcat 6.0.32, and Tomcat 7.0.8 are now fine.

  64. “you think about the potential damage caused to an ecommerce site by a mere set of some thousand requests, this is an absolute prio 1 issue.”

    My app isn’t an ecommerce site, but it does have hooks to vendors and partners outside my corporate net. So this should be a big deal for me, as well.

    However I’d like to have the vendor give me their .02 worth on updating the java their app uses: there must be a reason why they ship 1.5 . . .

    The vendor doesn’t seem to share my sense of urgency: still waiting for them to call me back, have attempted to escalate the ticket, getting phone tree hell.

  65. @gbt:
    I told a customer to patch his shop. His IT told me: We’re using IBM JDK, so nothing to do here.
    Well, I couldn’t resist but ran a http-accept-language-you-know-what-I-mean request against his shop, and it took veeeeery long to process.
    So I assume that IBM JDK is affected as well.

  66. @gbt:

    IBM JREs are affected. See comments #81 and #99.

    @Brian Dunbar:

    All Java versions dating back to 1.3 or maybe even 1.2 are affected. You do not need to update Java: Oracle has released a tool that patches the faulty rt.jar files.

  67. @ths,

    The second sentence in the abstract at that link says “This problem only occurs with 2.2250738585072012e-308, which also happens to be the largest floating point number” (neither are correct).

  68. I tried this on my .NET 3.5 dev env out of curiousity. No such glitch exists in C# at run-time or compile-time:

    double d1 = Double.Parse(“2.2250738585072012e-308”);
    Console.WriteLine(“Value: ” + d1);

    double d2 = 2.2250738585072012e-308;
    Console.WriteLine(“Value: ” + d2);

    If anyone can find anything similar in .NET I’d love to play with it, I can’t find anything like this, how boring!

Comments are closed.

Copyright © 2008-2024 Exploring Binary

Privacy policy

Powered by WordPress

css.php