03.02.11

Working on the JS engine, Episode IV

A testcase submitted to us today:

([][(![]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]()+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][(![]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]()+[])[!+[]+!+[]]

The result according to ES3, plus a common implementation-specific behavior, is the string "job".

The result according to ES5, plus a common implementation-specific behavior, is a thrown TypeError.

15 Comments »

  1. That may be the most awesome test case ever.

    Comment by Eric Shepherd — 03.02.11 @ 22:49

  2. Why is it the string ‘job’ in ES3 and a type error in ES5?

    Comment by Havvy — 03.02.11 @ 22:51

  3. That smells like a job puzzle if ever I saw one…

    Comment by Jan! — 03.02.11 @ 23:11

  4. In ECMAScript 3rd edition, calling nearly any method defined by the specification with this equal to either null or undefined acts as though the global object had instead been specified as this. In ECMAScript 5th edition, calling nearly any method this way throws a TypeError. The gibberish in the example does this at one point (where and how is left as an exercise for the reader), and consequences vary depending on whether the JS engine implements this aspect of ES5.

    The reason for the change is that inadvertent exposure of the global object like this is a hazard for “secure” JavaScript variants, which would dearly like to monitor and mediate all accesses to the global object. (You can find some of the primordial examples of “secure” JS variants demonstrated, and exploited, in these slides on Facebook’s secure JS system circa 2008.) Making built-in methods throw in this case seals off this vector to accessing the global object (which can be “exploited” through code as simple as valueOf() or [].sort.call()). Requiring that “secure” JavaScript be strict mode code seals off the equivalent vector for user-defined functions — for a user function foo which is in strict mode code, foo() will see the exact value undefined as its this.

    The gist of it is that, with these two changes (plus some care in the supporting JavaScript to implement a secure variant), it’s no longer possible to get direct access to the global object from secured code unless it’s explicitly given to you.

    Comment by Jeff — 04.02.11 @ 00:34

  5. I could see small bits of it being such a puzzle. For example: “Using only the characters +![](), write code which evaluates to the string 'fred'.” That’s small enough to be doable, I think.

    But I wouldn’t go too far with this, because it’s fairly obscure corners of the language, and I’m not sure how much coaching you’d need to do to get someone through it who can’t quite grasp the trick of it. And it is arguably a bit of a “trick”, that might not actually expose what someone knows or can figure out about the language.

    It also doesn’t help that some of the primitives are pretty daunting. The generator for the example takes over four hundred characters to produce code evaluating to "g". That’s a bit out of scope for an interview. :-)

    Comment by Jeff — 04.02.11 @ 00:42

  6. I really can’t understand how that code works.
    What the heck [!+[]+!+[]] (for example) does and why?

    Comment by Marco — 04.02.11 @ 01:46

  7. I will not explain the code: consider it an exercise for the reader. :-) Try breaking it down into its constituent expressions and operators, then gradually simplifying those to determine the ultimate operations performed. The ES5 specification may be helpful in doing this.

    (I am as yet uncertain whether I will permit comments here which explain the code. The post was written partly to amuse and partly to intrigue, and revealing the tricks hinders those aims. And really, it’s not that hard to understand once you get past the symbol shock!)

    Comment by Jeff — 04.02.11 @ 01:59

  8. Anyone know whether can the test case be rewritten to work under JS5, or does it rely on subtle behaviour of the global object?

    @Marco: + actually has three uses, although only two were used in the extract that you quoted.

    Comment by Neil Rashbrook — 04.02.11 @ 03:00

  9. (![] + []);           // "false"
    [!+[] + !+[] + !+[]]; // [3]
    "false"[3];           // "s"
    

    And that’s just a small part of the code … but it shows, how it basically works.

    [Note from Jeff: The comment went into a bit more detail than this, but I eventually decided it was more than I wanted to allow to be posted. So I pulled out a few illustrative parts and removed the rest. Apply a bunch of variations of the above trick and you can derive a lot of different strings, operations on them, and so on — possibly enough to recreate any string. I think with the ES5 change you can still execute arbitrary code this way, but this assumes an unmodified browser script environment. Make a couple changes to that starting environment, as “secure” JavaScript variants would do, and you may well be effectively corralled.]

    Comment by nikic — 04.02.11 @ 04:07

  10. At some point I get to []['sort']['call'](), but that doesn’t evaluate anymore :(

    Comment by nikic — 04.02.11 @ 04:37

  11. This is amazing. I am showing this to all my coworkers.

    Comment by Dan — 04.02.11 @ 05:03

  12. I like it! I spotted it yesterday in the jobs test, which has a few nice bits of obfuscation going on. It’s quite repetitive, so you can see how the chunks in it were made by copy-and-paste. I had to run through it before I worked out where the “j” came from, but it’s a clever way to get it, and the “b”.

    Comment by Nicholas Wilson — 04.02.11 @ 07:14

  13. I think it can be rewritten to work with ES5, but I might be mistaken. Kill off some standard properties beforehand, however, and you might be stuck.

    Comment by Jeff — 04.02.11 @ 10:10

  14. ([]+{})[!+[]+!+[]+!+[]]+([]+{})[+!+[]]+([]+{})[!+[]+!+[]]

    So, can anyone beat 57 characters?

    Comment by Neil Rashbrook — 06.02.11 @ 02:44

  15. Depends what the rules are. The original here used only the characters !+()[], making it a good deal more difficult to access Object.prototype.toString than if you have {} as well. But to be sure, it fits the spirit of the idea.

    Comment by Jeff — 06.02.11 @ 12:37

RSS feed for comments on this post. TrackBack URI

Leave a comment

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>