You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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?
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.
Rust does already support full ASLR / full RELRO via -C link-args="-pie -Wl,-z,relro,-z,now"
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.
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 defense rather than security through obscurity, full RELRO is required
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.
and at the moment that means forcing on immediate binding but it might not in the future.
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.
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.
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.
The stack canaries cause a 1-10% performance hit and a 1-15% code size increase.
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.
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.
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:
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.
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.
harder to exploit this in many cases, but it's still a significant weakness
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.
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.
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.
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)].
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 is pic. That's already the default model, so it will be enabled by default everywhere. It may make sense to use dynamic-no-pic by default on architectures like i686 where position independent code is expensive, but that's a separate issue. See #16340.
Activity
kmcallister commentedon Jun 25, 2014
LLVM already supports stack canaries, so I would start with #15180, and then try the various
sspattributes 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-Hsimilar 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
mmapprecisely 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
\0to 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
-Hthough. :) I want to make "more secure and somewhat slower" a preference that users can easily express, similar to how-Oexpresses "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 (
unsafeand 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
unsafeto 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-levelwill 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-Halso 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
picrelocation 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-picby 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...
steveklabnik commentedon Sep 3, 2015
Triage: still a hodge-podge of some things, but there's more that can be done.
19 remaining items
add rustc option for using LLVM stack smash protection
Rollup merge of rust-lang#84197 - bbjornse:stack-protector, r=nikic
Rollup merge of rust-lang#84197 - bbjornse:stack-protector, r=nikic
add rustc option for using LLVM stack smash protection
Auto merge of rust-lang#84197 - bbjornse:stack-protector, r=nikic
Auto merge of rust-lang#15179 - ponyii:fix/default-values-of-const-pa…
FIX: NegMultiply should preserve parenthesis when method is called (r…