Hacker News new | ask | show | jobs
by comex 4006 days ago
Interesting. I haven't fully digested the paper, but a few notes for context:

- Most real-world exploits these days are based on use-after-frees, heap buffer overflows, and other heap-related weirdness, rather than stack buffer overflows. It's nice that SafeStack mitigates that attack vector though (but if you disable stack canaries in favor of it, you actually reopen the door to exploit certain types of vulnerabilities...)

- A (the most?) common method to proceed from memory corruption to return-oriented programming is to redirect a virtual method call or other indirect jump to a stack pivot instruction. SafeStack alone does nothing to prevent this, so it doesn't prevent ROP.

- However, the general code-pointer indirection mechanisms described in the paper, of which SafeStack is an important component, could make ROP significantly harder, because you would only be able to jump to the starts of functions. This guarantee is similar to Windows's CFG (although the implementation is different), but SafeStack makes it harder to bypass by finding a pointer into the stack (either on the heap or via gadget).

- In practice, interoperation with unprotected OS libraries is likely to seriously compromise the security benefits of the combined scheme, because they will store pointers into the real stack, jump directly to code pointers on the heap, etc. JIT compilers are also likely to be problematic.

- In addition, there are more direct ways for an attacker to work around the protection, such as using as gadgets starts of functions that do some small operation and then proceed to a virtual call on an argument. The larger the application, the more possibilities for bypass there are.

- Still, "harder" is pretty good.

Edit: By the way, the point about function start gadgets makes questionable the paper's claim that "CPI guarantees the impossibility of any control-flow hijack attack based on memory corruptions." Also, if you want to guarantee rsp isn't leaked, it isn't enough to keep all pointers near it out of regular memory: they also have to be kept out of the stack itself, because functions with many (or variable) arguments will read them from the stack - at least, I don't see a claim in the paper about moving them - so subverting an indirect call to go to a function that takes more arguments than actually provided (or just changing a printf format string to have a lot of arguments) will cause whatever data's on the stack to be treated as arguments. Ditto registers that either can be used for arguments or are callee-saved. That means frame pointers have to be disabled or munged, and any cases where LLVM automatically generates temporary pointers for stack stores - which I've seen it do before - have to be addressed.

If you do move non-register arguments to the safe stack then the situation is improved, but you still have to watch out for temporaries left in argument registers.

1 comments

SafeStack is essentially an IA-64/SPARC-style two-stack model - there are no pointers to the %rsp stack.
The issue is preventing pointers to the real stack on the real stack. I'm pretty sure you can't do that reliably at the LLVM IR level, since as I said such pointers can be introduced during code generation. In fact, I just looked at the source to the merged pass, and it doesn't even try - it only checks if stack pointers are passed to calls, but e.g.

    int *p = cond ? &a : &b;

    ...later enough that this isn't trivially optimized into two stores...

    *p = 1; 
will probably not be flagged (it depends on what optimization passes have run before the SafeStack pass), but will put the pointer in the stack or a register that may be saved.