4 ms·
It's common for C++ code to do type checking dynamically. In WebKit we do this by rolling the type checks ourselves. With RTTI, you can do dynamic type checks u
by pizlonator 9y ago
It's common for C++ code to do type checking dynamically. In WebKit we do this by rolling the type checks ourselves. With RTTI, you can do dynamic type checks using the build-in dynamic_cast primitive. Our changes, particularly the ones having to do with pointer poisoning, are meant to protect C++ code that does dynamic type checks in addition to JIT'd JS code that does dynamic type checks.
Rust's `match` statement is a dynamic type check just like C++'s `dynamic_cast`. I bet you it's implemented using branches.
- kibwen 9y ago> Rust's `match` statement is a dynamic type check just like C++'s `dynamic_cast`. I wonder if this is a case of a difference in terminology, because enum variants (the branches in `match` expressions) aren't considered types in Rust, and have no interaction with Rust's typechecker (Rust always has to assume that every instance of an enum can be any variant, even in cases where we as the programmer know that only one variant is possible). At the same time I'm still unclear on what the OP is trying to imply. To reiterate, AIUI Spectre can only read, not write, privileged memory, so it's impossible for a Spectre attack to trick the engine into believing that (to use Rust terminology) an instance of an enum is a variant that it isn't. Am I incorrect?
- bzbarsky 9y agoThe idea is to read random things as follows: 1) Write a function that reads the "nth" value out of an array in JS. 2) Call the function a bunch of times with JS arrays. 3) Pass an integer to that function for the "array" value. This would normally end up throwing. But before it does, the CPU might speculate the VM-internal typecheck as "it's going to be an Array, like the previous 1000 times" and end up doing an "nth value" read out of a memory address that you fully control (by changing which integer you pass). That is, you can use speculative type confusion in the VM to allow precise control over what memory addresses get accessed and how for your timing attacks on the cache.
- petascale 9y agoHypothetical example pseudocode: if is_pointer(pt): // do pointer-based stuff else: raise error If you train the branch predictor to expect a pointer, it will speculatively treat arbitrary values as pointers until it can determine that they are not. So you can pass in any value and get it treated like a pointer for the duration of the window of speculative execution. Any conditional branch is potentially vulnerable, an attacker just needs some sort of side effect from speculative execution that persists after rollback.
- kibwen 9y agoThanks for the reply, I think I've got it now: the ultimate attack is still to read arbitrary memory, and the part about defeating typechecking and crafting a pointer is intended to expand the range of memory that can be read.
- techdragon 9y agoSerious question, is it even possible to have something like ‘match’ or ‘dynamic_cast’ in a programming language without using branches under the hood? I’m not talking about research/example microprocessors that don’t have equivalent instructions. I mean isn’t branching a fundamental logical construct and we either do it in hardware with branch assembly operations or emulate functional equivalents of them in the software that runs on the microprocessor some other way? Or is there some clever/old logical/mathematical “alternative” to branches?
- cerved 9y agoThe problem isn't the branches it's the speculative execution
- earenndil 9y agoConsider something like this: struct __internal_variable { uint64_t type; void *data; } uint64_t __last_type = [number of builtin types]; whenever you create a new type: increment __last_type and associate that type with the number; uint64_t typeof(__internal_variable var) { return var.type; } function[__last_type] typechecks; functions[] = void function(uint64_t type) { failure; } functions[int] = void function(uint64_t type) { success; } functions[typeof(myvar)]() If typeof(myvar) is int, then the function that returns success will be called, otherwise failure, no branches involved! Yes, it's a kludge, but it kind of works. --- Of course, you don't actually need this trick. If you put enough effort into it, you can essentially compile anything to c, after which you can just compile it with the movfuscator[1] 1: https://github.com/xoreaxeaxeax/movfuscator https://github.com/xoreaxeaxeax/movfuscator
- gpderetta 9y agoThat's basically a jump table. Still vulnerable to spectre (variant 2) without mitigations, btw.
- fpoling 9y agoIt is even worse then switch/match statements. The jump tables can be used to train CPU to jump to a wide variety of addresses than switch/match statements so mounting a speculative execution attack is simpler.