Closed
Description
This is tremendously open-ended, but at minimum we should implement the usual tricks from C compilers, such as
- Full ASLR
- Stack canaries
- Struct layout randomization — @huonw already wrote a plugin for this
- Read-only pages with dynamic relocations, and eager PLT resolution
- ASan support (Investigate running tests under Address Sanitizer #749)
This will protect unsafe
code, and will mitigate the impact of compiler bugs. Some of it will also protect buggy C code when it's linked with Rust.
The goal here isn't just to make these things possible but to have really painless toolchain support. In many cases the performance impact is insignificant and there's no reason not to compile with mitigations.
Beyond the established techniques, there are a lot of interesting research ideas we could implement. See for example Prof. Michael Franz's talk at Mozilla on compiler-generated software diversity.
Metadata
Metadata
Assignees
Labels
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
kmcallister commentedon Jun 25, 2014
LLVM already supports stack canaries, so I would start with #15180, and then try the various
ssp
attributes to see if they produce correct code with effective mitigations.I think we should have a high-level compiler flag which enables a reasonable set of mitigations. Perhaps
--harden-level=
, and-H
similar to-O
?thestinger commentedon Jun 25, 2014
Rust does already support full ASLR / full RELRO via
-C link-args="-pie -Wl,-z,relro,-z,now"
.In order for ASLR to be useful, you need to prevent leaking a pointer to any symbol as it gives away the randomized base, and Rust permits this in safe code. To be truly useful as a statistical defence rather than security through obscurity, full RELRO is required, and at the moment that means forcing on immediate binding but it might not in the future.
Chromium also does ASLR in userspace for memory allocations, because the OS ASLR is usually very weak. For example, Linux without PaX patches will still just lay out each
mmap
precisely after the last one as it only ever randomizes the starting point. PaX and Chromium will add a small random gap instead, so it causes memory fragmentation for the sake of improved randomization. This doesn't play well with jemalloc, because it wants full control over how allocations are laid out.I'm not sure how much of this is sensible to pursue for Rust. The stack canaries cause a 1-10% performance hit and a 1-15% code size increase. They're aimed at stopping vulnerabilities due to C strings more than anything else and include a
\0
to stop those functions from going past the end of the stack frame.kmcallister commentedon Jun 25, 2014
Cool. That's quite a mouthful compared to
-H
though. :) I want to make "more secure and somewhat slower" a preference that users can easily express, similar to how-O
expresses "faster to run and slower to compile and harder to debug" without getting into the details of every optimization pass.Sure, Rust allows creating such a leak in safe code, but it's not the most common thing to do, and the attacker has to find a way to access it in the relevant attack scenario (which could be remote or have a limit on attempts), and even if they do find the leak, you've forced them to spend resources doing so, which is what these mitigations are all about.
I don't think the question of what things are possible in safe code is very relevant, anyway. For any code where these mitigations come into play, the memory safety system has already failed, or doesn't try to provide any guarantees (
unsafe
and foreign code). The safe dialect shouldn't and doesn't need to prevent you from doing anything that might decrease the effectiveness of exploit mitigations; they are fundamentally an unsound, defense-in-depth thing.Of course it would be easy to get such a leak in a system which uses
unsafe
to sandbox attacker-controlled code. But in that case you're already trusting the typechecker to a huge degree, and it seems moot to complain that it can't also prevent loopholes in a last-ditch countermeasure.There's no actual sharp line between "security through obscurity" and "statistical defense"; it's all about how much the attacker spends versus how much you spend. And ASLR is very cheap on AMD64 (see my benchmarks). It's probably cheap on ARM too, although I haven't checked. The question of which mitigations are enabled at which
--harden-level
will certainly be platform specific.Interesting; how do you do RELRO without immediate binding?
Also I don't understand why RELRO is a prerequisite for useful ASLR. In my understanding, ASLR is about making it hard to find out where things are. RELRO is about preventing writes to certain executable pages (inter alia) even knowing where those pages are. So if you have some RWX pages, but it's hard for the attacker to find them, that's still a win.
Dang, I didn't realize Linux's ASLR is that bad >_>. But static exec ASLR is still a big win. Programs that aren't doing dynamic code loading / generation will have all their executable pages randomized, which is important for preventing ROP.
Anyway with this ticket I mostly had compiler features in mind. Hardening allocator libraries seems pretty separate although I would certainly be happy to see that as well. There is plenty of allocator hardening you can do beyond randomization, as well.
Ubuntu builds all packages with stack canaries by default. They do PIE for ASLR on certain high-security packages and would do it for everything on AMD64 if not for issues of compatibility with existing code. Debian also hardens many packages. Mosh built from source uses whatever hardening is supported by the platform.
My point is that this stuff is even today becoming the norm, and if we don't support it, that's a serious regression from C to unsafe-Rust, or even Rust that links C libraries (recall that a non-PIE Rust binary will provide ROP gadgets for an exploit in a perfectly hardened C library). Yes, there is sometimes a performance penalty, and users can decide how they feel about that, much as they decide whether to use
-O
.I don't think that's fair or accurate. Sure, an AMD64 Linux glibc canary contains one NULL byte, it might as well. But it also contains seven random bytes. Checking those bytes before return will catch attempts to overflow stack buffers that aren't strings. I'm confident I can find many examples of this mitigation being effective in practice.
Actually I suspect that NULL byte is there, as the LSB i.e. first in memory, to stop C string functions from reading the canary value and leaking it to the attacker, who can then include it in a stack-smash attempt that might have nothing to do with strings and can happen in a completely different function.
For anyone interested, here's code to print the stack canary on AMD64 Linux:
thestinger commentedon Jun 25, 2014
RELRO makes tables of function pointers (GOT) read-only. In a binary not compiled as a position independent executable, these are in predictable locations. A position independent executable makes it harder to exploit this in many cases, but it's still a significant weakness because it allows for control of the program's executable via pointers known to aim somewhere into that writeable data.
https://isisblogs.poly.edu/2011/06/01/relro-relocation-read-only/
kmcallister commentedon Jun 25, 2014
Yeah, that's basically the name of the game here. It seems really unfair to dismiss ASLR as "security through obscurity" just because it can be worked around sometimes.
thestinger commentedon Jun 25, 2014
I guess that's true, but it's a lot more valuable when combined with other mitigations like RELRO and when the application / library code is written or audited with info leaks in mind. An example of a feature interacting poorly with this is repr (aka Poly,
{:?}
in format strings), as it prints all of the raw pointer addresses in private fields and it's easy to use it without realizing it's going to do that. On the positive side, that's hidden away in a libdebug crate now.kmcallister commentedon Jun 26, 2014
I hadn't thought about the fact that
{:?}
format strings expose that information. It would make sense if-H
also enables certain lints, and that could include a warning about{:?}
. We probably want such a lint anyway.We could introduce something kind of like the stability attributes but meaning "this item shouldn't be used in production code" even when the interface is stable. You'd enable this lint on production builds, and you could still use
{:?}
within code gated by#[cfg(debug)]
.kmcallister commentedon Jun 27, 2014
This is rust-lang/rfcs#145.
thestinger commentedon Aug 8, 2014
AFAIK PIE is the only reason for using LLVM's
pic
relocation model for an executable, so it can simply be enabled if the relocation model ispic
. That's already the default model, so it will be enabled by default everywhere. It may make sense to usedynamic-no-pic
by default on architectures like i686 where position independent code is expensive, but that's a separate issue. See #16340.thestinger commentedon Aug 15, 2014
#16514 covers providing full ASLR on Windows, as is already the case on Linux
thestinger commentedon Aug 16, 2014
#16533 covers enabling DEP (NX bit) support for all Windows executables
thestinger commentedon Sep 18, 2014
#17161 disabled ASLR on Windows...
38 remaining items