2

Some browsers like Chromium and recently Edge have a flag to disable JIT. What attacks/threats does this protect against and what JS based attacks are not prevented?

2

JIT compilers are at risk of an attack-aiding technique called "JIT spraying", which is a way to get around NX (data execution prevention) protections when dropping a shellcode. The idea is that you create a large number of mathematical operations involving very large constants (32-bit or even 64-bit, using all the bytes) in your JS (or other language to be JIT-compiled), and when the compiler converts them into machine code and makes the resulting page in memory writable, you have a ready-made program (or at least collection of executable gadgets) if you jump into it at an offset. That is, because you control every byte (arbitrary control over the ones in the constants; the ones in the opcodes are predictable but more restricted), you can create code that does something totally under you control, if you enter it offset from an actual instruction boundary. This is the same approach used to create ROP gadgets, but unlike ROP gadgets (which are often in other libraries, whose addresses are randomized by ASLR), with a JIT spray you can create the perfect shellcode, including a NOP sled if your exploit doesn't afford precision targeting.

Note that JIT spraying is not itself an exploit, any more than ROP is; it's a way to get from "control of the instruction pointer" to "arbitrary code execution" on a modern machine with modern exploit mitigations (like NX, ASLR, etc.). The attacker still needs an exploit - typically a memory corruption - to gain that control.

JIT spraying does have mitigations of its own. One option is "constant blinding", where the JIT randomly selects an XOR mask and, every time it would emit an operation containing a constant (or "immediate") operand, it instead XORs the constant with the mask, and emits two instructions (an XOR that undoes the step the JIT just did, and the original operation upon the unmasked operand now present in a register). However, constant blinding has a performance impact (additional ALU operations, decreased cache density, increased register usage). Other possibilities include doing something similar to ASLR on a local scale (scattering compiled machine code haphazardly, on the basis that any given spray will now be fragmented) or inserting NOPs (not necessarily the classic opcode for them, just any non-state-changing operation) between real instructions (on the basis that this will break non-aligned instruction streams). Obviously these also have performance impact. Of course, none of these mitigations cost nearly as much performance as turning JIT compilation off entirely...

Another security risk with JIT compilers is that it increases attack surface. Compilers have bugs sometimes, like any complicated piece of software, and a known compiler bug can sometimes be used to generate illegal (per the language spec) operations such as writing to addresses that aren't supposed to be under user control (e.g. directly setting the address or length of an array's backing buffer). Interpreters also have tons of attack surface, but usually can't be turned off (even with JIT enabled, some code gets passed to the interpreter) so disabling JIT is still a reduction in total attack surface.

Finally, some attacks require the malicious code to be "close to the metal" in a way that interpreters don't really support, but compilers (including JIT) do. For example, consider the (original) Spectre CPU bug of four years back. Exploiting Spectre required executing in very small windows of time (performing a memory lookup before the speculative execution branch was terminated), using very precise timing to measure the results (difference between a cache hit and a cache miss), and of course required that the target address not be evicted from the cache before being tested. This is relatively easy with compiled code, but an interpreter might well have produced so many extra steps (including slow and cache-modifying memory accesses) that it would be totally impractical to get the tight code and accurate timings needed.


Now, with all that said... the typical reason you might want to disable JIT is actually not security at all. As I said above, compilers can be buggy, and JITs are no exception. Even aside from security risks, somebody investigating a bizarre misbehavior in their code might disable the JIT to see if that was the cause. Additionally, JITs are considerably less portable than the rest of the browser (by their nature, they must be specific to every instruction set and sometimes OS), so somebody working on porting a browser to a new platform might disable the JIT by default in early releases (even after it's nominally working) due to higher-than-normal risk of bugs causing script errors.


As for what JS-based attacks are not prevented, anything in the JS runtime (which is where most security bugs are found). For example, a double-free, use-after-free, buffer overflow (due to failure to check bounds or integer overflow or so on), etc. While these bugs might manifest differently when JIT is used or not, the underlying logic bug in the runtime that causes e.g. an object to be garbage collected while still available to the code (use after free) is typically going to manifest regardless of whether the user-supplied code is compiled or not. Such vulnerabilities have been a threat to JS engines since they were introduced in the 90s.

1

I can't find any flags in chrome for this, but the edge security team has given their rationale for adding the #super-duper-secure-mode flag.

Essentially, they have two security-relevant reasons for adding this flag. First (as CBHacking has already mentioned), the JIT engines used in browsers are incredibly complex as they try to add every possible optimization. This adds significantly to the attack surface, and they posit that around half the vulnerabilities in the chromium V8 engine are related to the JIT compiler.

The second reason they want to disable it is that including the JIT compiler prevents them from using some modern exploit mitigations in the browser, namely: Intel's hardware backed Control flow Enforcement Technology and Control Flow Guard, as well as Windows' Arbitrary Code Guard. These mitigations make it significantly more difficult to exploit many memory corruption bugs but unfortunately are incompatible with JIT compilers. So disabling JIT will also increase the cost of exploiting several types of bugs in other components of the browser.

Of course, disabling JIT compilation comes with the cost of reduced performance. Whether or not the performance hit is significant seems open to debate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.