4 ms·
> there's zero reason to blame C++ Seems like UAF in C++? So, yes, I feel comfortable blaming C++. Anyways, I didn't mention memory safe languages in my post
by staticassertion 8y ago
> there's zero reason to blame C++
Seems like UAF in C++? So, yes, I feel comfortable blaming C++.
Anyways, I didn't mention memory safe languages in my post, and I'm not even suggesting that Google did anything wrong, or should change their posture in any way. They've been extremely successful.
- wahern 8y agoUAF by whom? If it's through a bare reference held in the JavaScript VM where operations don't access the C++ object as a C++ object or as some shared primitive object as in the JVM or CLR, then the error is in maintaining state consistency across distinct environments. Such bugs look exactly the same in every language, although the risk is compounded by (a) JIT'd environments and (b) asynchronous execution. In the context of a bug involving JavaScript ArrayBuffers (where JIT compilers are designed to optimize away both FFI accessors and bounds checks) and asynchronous events I think it would be prudent to withhold judgment.
- tptacek 8y agoCan you provide an example of a dangling reference of any type (not just a UAF) being exploitable for RCE in a managed language like Java, Rust, or Go?
- microcolonel 8y ago> Can you provide an example of a dangling reference of any type (not just a UAF) being exploitable for RCE in a managed language like Java, Rust, or Go? The point is that it doesn't matter what language is hosting the JIT. You can write this same bug in a program with a JIT hosted on the JVM, in a Rust program, or in a Go program. The code generated by the JIT is the equivalent of C FFI or an unsafe block, which completely ignores your "managed" language. V8 calls directly into and out of the JIT heap with a couple stubs a handful of instructions long, code in the JIT heap is whatever you generated, and can do whatever you told it to.
- pcwalton 8y agoOK, but this bug isn't in jitcode.
- wahern 8y agoI never said it was. I said it implicated JIT'd code and the complex GC subsystem. The context is a JavaScript ArrayBuffer, the very type of object that JIT engines are built to optimize access to, and thus it's reasonable to infer that there may be some sort of cross-runtime scoping aspect at play. Such code by definition not only bypasses language protections, most relevant to this case it implicates interfaces that, in the parlance of Rust, could only ever be implemented using unsafe{} blocks. Without knowing more we can't meaningfully perform root cause analysis. Imagine passing a pointer to the backing store for Rust or Go arrays across an interface boundary. Imagine a JavaScript implementation also written in either of those languages, but where the entry point interfaces prevent the language (caller or callee) from tracking and ensuring lifetime invariants across the boundary (certainly not statically, nor dynamically because we're not using a unified abstract VM like the JVM or CLR). Correct memory management necessarily relies on the programmer explicitly and manually synchronizing liveness between those distinct environments. If they get it wrong it's not Rust or Go's fault, nor can you squarely lay blame on either side of the interface boundary; it's the programmer's fault and/or poor interface design; and the likelihood of such a bug is equally likely in Rust, Go, or C++. JIT also comes into play because even if the entirety of Chrome or Mozilla were nominally implemented in Rust or Go, the compilers and runtimes of either language could not ensure correctness of the generated JIT'd code nor track dependencies across native Rust/Go code and the generated JIT'd code--particularly for JavaScript code where the norm is object lifetimes escaping function scope. JIT'd native code effectively, if not formally, results in two distinct, disjoint runtime environments. Correctness, again, would be predicated on correct use of interfaces that might look suspiciously like malloc/free. The better interfaces would expose and emphasize the semantics of anchoring and referencing binding directly, but such interfaces are difficult to design and implement--after all, if they were then the argument that safe C++ is too difficult in practice wouldn't have such weight. There are entirely interpreted JavaScript engines. Rhino is a obvious example for Java. Duktape is a nice, embeddable interpreter written in C. Theoretically it could be rewritten in Rust, but that wouldn't make it any more likely to displace implementations like V8. And that's my point. In reality the fundamental problem isn't that we're not using a Rust-written Duktape (it's manifestly not a realistic expectation, not in the domain of JavaScript engines), it's that we don't invest enough attention in integration across inevitable language boundaries.
- deleted 8y ago[deleted]
- fooker 8y agoYes, sure. You write to a file, save the file name or file descriptor. Then when you read it back later, maybe you forget to validate the contents. This can be exploited depending on what you were doing with the data. Do you now want to restrict what you mean by a 'reference'? You'll see that once you keep restricting that enough you first lose the ability to write fast programs and eventually useful programs(evidence: various 'unsafe' modes in so called safe languages).
- titzer 8y ago> where JIT compilers are designed to optimize away...bounds checks V8 does not optimize away bounds checks on array buffers. For one thing, array buffers can be detached, so their length can suddenly drop to zero.
- wahern 8y agoIt does for native arrays, at least: > This is the incorrect optimization we’re looking for. JavaScript array accesses are guarded by CheckBounds nodes, which ensure that the index falls within the array bounds. If the optimizer can determine statically that the index is always in-bounds, it can eliminate the CheckBounds node. Image tying the index to the result of Object.is: since the typing information is off, we could make the analyzer think the index is always in-bounds, while it can be out-of-bounds at runtime. The optimizer will incorrectly eliminate CheckBounds, giving us OOB access to a JS array, which we can use to construct more powerful exploitation primitives. https://abiondo.me/2019/01/02/exploiting-math-expm1-v8/ https://abiondo.me/2019/01/02/exploiting-math-expm1-v8/ See also https://halbecaf.com/2017/05/24/exploiting-a-v8-oob-write/ https://halbecaf.com/2017/05/24/exploiting-a-v8-oob-write/ Microsoft's Chakra engine: https://www.thezdi.com/blog/2017/10/5/check-it-out-enforcement-of-bounds-checks-in-native-jit-code https://www.thezdi.com/blog/2017/10/5/check-it-out-enforceme... Hoisting a bounds check outside a loop is a common optimization, and I would be surprised if V8 doesn't do this for ArrayBuffers. But in any event my point is that the JIT compiler is in effect doing manual memory management outside the visibility of the host language, so for these types of bugs a memory-safe host language doesn't save you. And it can get this wrong and will get this wrong because these are extremely complex systems with high code churn.
- titzer 8y agoJavaScript arrays and ArrayBuffers are totally separate animals.