5 ms·
They say that Rust is not enough and dismiss it quickly: > V8 vulnerabilities are rarely "classic" memory corruption bugs (use-after-frees, out-of-bounds acces
by egnehots 2y ago
They say that Rust is not enough and dismiss it quickly:
> V8 vulnerabilities are rarely "classic" memory corruption bugs (use-after-frees, out-of-bounds accesses, etc.) but instead subtle logic issues which can in turn be exploited to corrupt memory. As such, existing memory safety solutions are, for the most part, not applicable to V8. In particular, neither switching to a memory safe language, such as Rust, nor using current or future hardware memory safety features, such as memory tagging, can help with the security challenges faced by V8 today.
But looking at the awesome list they provided:
https://docs.google.com/spreadsheets/d/1lkNJ0uQwbeC1ZTRrxdtuPLCIl7mlUreoKfSIgajnSyY/edit?usp=sharing https://docs.google.com/spreadsheets/d/1lkNJ0uQwbeC1ZTRrxdtu...
There are a lot of use-after-frees and out-of-bounds accesses, buffer overflow in there...
- kevingadd 2y agoType confusion is also a very common attack against JS runtimes and V8 specifically. Of course, it's not trivial to build a high-performance JS runtime without playing around with pointer types pretty liberally, so I can understand saying "Rust won't fix this" in regards to those attacks. But those attacks would basically not be possible against a runtime built on top of Java or C#.
- sroussey 2y agoThe attacks would not be possible against a runtime written in JavaScript as well, by that reasoning.
- olliej 2y agoYes because the attack would be against the .net or Java VM. The JVM - especially in the era of applets - had an illustrious history of VM bugs. We don’t know how bad they would have been because in the era of extremely complex exploits applets essentially do no exist. Neither .net nor the jvm are exposed to the degree of attacks the js engines are, and there’s no strong reason to believe they don’t have similar bugs today.
- kevingadd 2y agoI'm not singing the praises of the JVM here, it's just a simple fact that if you implement your runtime in a higher level language you're exposed to a smaller number of potential vulnerabilities. Unchecked array dereferences turn into bounds-checked array dereferences; unchecked typecasts turn into checked typecasts. Null pointer dereferences turn into null reference exceptions. Etc. Of course once you start jitting native code, all of that is off the table. Unless you jit to java/.net bytecode, I guess.
- olliej 2y agoNo, you're missing the point. The whole point is you're implementing the runtime that defines the safety semantics. Your proposal is essentially "implement your JS engine GC on top of the JVM by just using the JVM's GC", i.e. don't implement the GC yourself. The unsafe code is now the JVM GC, and you've just moved the problem from "implement the JS engine's GC" to "Implement the JVM's GC", and they same problems continue to exist. I am really struggling to understand where this gap in understanding is occurring. It does not matter what environment or language you implement a JS engine (or whatever) in. The attacker is going to attack the unsafe portion of the runtime. If you build you JS engine on top of the JVM, then the attacker is not going to attack your JS engine's runtime, they attack the JVM's. The JVM, .NET, etc runtimes are not doing anything different to what the JS engine runtimes are doing, and aren't magically free of the same bugs. If anything they're probably doing less to protect from or prevent attacks, because they have a much much smaller attack surface (because they aren't generally exposed to everything on the internet) and the reason attackers have to target the JS engine runtime is because the JS sandbox does not allow the general system access "correct" and completely uncompromised .NET or JVM code have. Attacks on the JVM and .NET generally mean "convince the VM to load correct code that does something that a specific app/service is not meant to do but the VM generally allows applications to do", whereas a JS VM does not allow an attacker to do anything outside of the JS sandbox, so they must compromise the runtime. It may be easier to understand if we try to present this in a different way: JSC can be compiled as an interpreter for any cpu architecture because there is a fall back C backend for the interpreter code generator, so you can compile JSC to WASM. Then you could make a version of webkit than executed all JS through the WASM build of JSC running under the native JSC runtime. You've now built your JS engine on top of a safe runtime (WASM), but it should hopefully be obvious that an attacker is simply going to continue targeting the native JSC runtime.
- vlovich123 2y agoWould be interesting if they took hardening ideas from kernels that try to solve this (e.g. https://security.apple.com/blog/towards-the-next-generation-of-xnu-memory-safety/ https://security.apple.com/blog/towards-the-next-generation-...).
- ngrilly 2y agoV8 is a runtime for JS exactly like the JVM is a runtime for Java and CLR is for C#. Which means that whatever sandboxing V8 needs, the JVM and the CLR would need it as well. I don't know what makes you think that the JVM and the CLR have already solved the problem, but not V8.
- olliej 2y ago> There are a lot of use-after-frees and out-of-bounds accesses, buffer overflow in there. Yes, and they’re in the runtime itself, which rust cannot protect you from. Rust cannot protect lifetime enforcement for GC objects any more than C++ already does, it can’t protect you against OoB when the reason for the OoB is the runtime is wrong about the object size, etc. Rust does not magically make it impossible to have errors, it makes it harder by default, but the cases where these go wrong are already largely using c++ to provide the same level of memory safety rust can in the environment. The easiest way to understand this is if you use `vec` you won’t get unsafe oob, but if there’s a bug in `vec` rust (or any language) cannot protect you. Eg if there’s a JVM bug that breaks arrays then the fact that Java is memory safe isn’t relevant.
- aapoalas 2y agoThis is not strictly true. If you took a magic Rust wand to the whole V8 codebase that converted it into exactly the equivalent Rust code, Rust would definitely not help you one bit. But the code would also be filled with unsafe blocks. Many commenters have been saying that this is because it must be, otherwise the engine couldn't be written. For the JIT side of things I definitely agree. But as for the runtime's static implementations (those written in C++/Rust) it is entirely possible to write the runtime in safe Rust which definitely would catch eg. the simple example error they use in the blog post. One option would be to use an existing gc crate: It checks at runtime that Rust's borrowing rules are upheld, so it would abort the program instead of doing the OoB access. This is of course not nice but it is memory safe. Obviously this would also make the engine less performant as now we're doing extra work on every read and write into a GC object. Another option is to let go of our idea about GC objects holding mutable pointer access to one another. V8 already uses offsets from a 4 GB offset to find the item; these are "compressed pointers" because they always know they need to just upshift a bit to get the actual pointer, and because C++ doesn't care about multiple objects holding pointers to one another. A Rusty alternative to this would be that the "compressed pointers" are considered only 32-bit offsets from some base pointer that is held by the Isolate: Now Rust would not allow these to be actual pointers or become references. Instead you'd need to implement some API at the Isolate that gives you a reference to an item based on that offset, and the reference's lifetime is determined by your Isolate's lifetime. Now, any call to JavaScript is always (in a theoretical sense) capable of mutating anything within the Isolate's heap. This means that calling a JS function would require an exclusive `&mut Isolate` reference: This now means that Rust understands that it cannot hold a reference to the Array's buffer (which it got a reference to from the Isolate heap) during a call to a JS function. This sort of API would internally need some unsafe because it is a pointer offset we're doing here. If you don't like that, you can also go a step further and build your heap as a collection of vectors and then have your "pointers" be a combination of a type key and an index an index into that type's vector. With this sort of heap structure there is no unsafe usage needed as getting a reference is just borrowing from a Vec within the heap. These "Rusty" ways of building the heap offer some interesting philosophical thoughts to ponder: The V8 way is kind of a faithful structuring of the ECMAScript concept of "object ownership" in C++: Items refer to each other directly, and can even do it recursively. Ownership of the memory is just kind of... there. It's obvious, right? This object refers to that, so it owns it. Except maybe if they're recursive and not accessible elsewhere... I mean, just don't think about it! (Unless you're building the GC algorithm.) The safe Rust heap structures make the memory ownership quite concrete: The Isolate owns all of its GC objects / everything in its heap. GC objects and references between them are NOT actual memory ownership relations! This JS object referring to that JS object means nothing to the memory ownership. A JS object exists as long as the Isolate thinks it exists, even if no other object refers to it. So, the C++ / unsafe Rust way to write the Heap tries to unify JS ownership logic and the host language ownership logic into one. When these two don't agree, bugs can happen (like in the simple example provided: JS Array semantics written naively in C++ cause issues because C++ has different expectations). The safe Rust way instead throws the JS ownership logic out of the host language entirely and forces the engine to implement that runtime ownership logic on its own. Source: I am writing a JS engine in (safe) Rust using the "heap is a collection of vectors of Ts" method.
- wavemode 2y agoYou didn't read the article, then. They clearly explain how even if Rust were used for the entirety of v8, there would still be memory corruption, because the memory corruption is happening in code that is JIT compiled.
- vlovich123 2y agoI think they did because all the vulnerabilities in the hardening they talk about is because of C++ memory safety & would be fixed by Rust (i.e. their hardening technique doesn't target JIT exploits themselves).
- tptacek 2y agoThe whole article is about exploits that leverage the compiler itself, with details.
- rcxdude 2y agoNo, it mentions that as an introduction, and then talks about the system for mitigating them, which also has bugs which they admit are of the simple kind that a memory-safe language would prevent.
- azakai 2y agoNo, this very much does help protect against JIT exploits. JIT code contains code that accesses the data structures they are sandboxing. By sandboxing those objects, the JIT code is limited in what it can do. This might help you understand: An example the article gives is if an optimization pass has a bug that forgets a check. Then it may emit JIT code that will access a data structure that it should not. But, thanks to this sandboxing, that object cannot be outside the sandbox, nor refer to anything outside the sandbox, so a JIT exploit is limited in what it can achieve.
- vlovich123 2y agoMy point was that the sandbox escape that is now required to exploit a JIT issue has nothing to do with JIT or things Rust won't help with. Indeed, the vast majority of sandbox escapes they've found are straight-up basic memory safety issues that Rust would protect against much better than trying to harden C++. Again, there's a real switching cost and 2nd system syndrome to consider, so I'm not saying "switch V8 to Rust" but ignoring that conversation wholesale is disingenuous, especially when it's a bait and switch (i.e. Rust doesn't help with JIT issues, here's this sandbox idea that does, except we wrote the sandbox in C++ & memory safety exploits in the sandbox/runtime are easily found & paired with the JIT exploit).