Really what Rust guarantees is memory safety; no undefined behavior caused by writing to or reading from memory in a way that is undefined by the language semantics.
One of the common ways that lack of memory safety manifests is via segfaults; if you have a dangling pointer that points to some unmapped memory, and dereference it, you will get a segfault. That's actually one of the better failure modes for memory unsafety; a segfault simply kills your program and indicates why, so you can fix it. Much worse are when there is a dangling pointer that points to valid memory, causing silent corruption of program state, sometimes in ways that are exploitable by outside attackers, sometimes in ways that are just very difficult to track down and debug.
Rust used to use __morestack
to detect stack overflow and panic in a deterministic way when that happened (and even earlier, used to use __morestack
for actual segmented stacks, though that went away a long time ago). However, in 1.4 Rust switched to using guard pages, so accessing memory beyond the stack (stack overflow) causes a deterministic segfault. Getting a segfault on stack overflow is not memory unsafe, it's a deterministic failure just like killing your program on running out of memory to allocate.
Now, there's one problem with that; the guard page segfault will only happen if you actually do write to the guard page. It's possible, however, to have an allocation that goes past the end of the guard page; and now you may be exposed to memory safety issues again if you only write to the portion that is beyond the guard page, and that happens to be mapped memory. To prevent this, you can use stack probes, which are just extra accesses to memory added to a function if its stack frame takes up more than one page, to trigger a segfault in the guard page in a deterministic way, but it looks like those are not yet implemented on all platforms.
So, this is currently a soundness hole in the current implementation of Rust. It is possible to overwrite arbitrary memory if, for instance, you allocate a large buffer on the stack (like the 64k buffer used for std::fs::copy
), are near the end of the stack. The stack grows downwards on most platforms, while zeroing out the buffer goes upwards, so you can overwrite some memory that is far beyond the end of the stack before finally getting to the guard page and getting your segfault.
Once that problem is fixed, you will still get a segfault when you overflow the stack and the stack probe hits that guard page, but it should happen before anything untoward has happened, simply killing your process.
edit to add: Since lots of people still see this post, it's a good place to put an update on the current status.
After a number of exploits were publicized taking advantage of a one-page guard region on stacks (none that I know of affecting Rust code, but there is a possibility that Rust code could be similarly vulnerable), a good deal of attention has been paid to this issue. That led to a renewed push to get the LLVM patches for stack probes merged (1, 2), and to add support for stack probes to rustc
. The latter is still in the process of being merged, but should land soon.
Additionally, many operating systems have applied patches which increase the guard region from a single page to 1 MB or more, reducing the chances that a buffer allocated on the stack (like the 64k buffer that had been used for std::fs::copy
) would overlap with allocations beyond the guard region.
Anyhow, once PR #42816 is merged and released, Rust should be free of this problem, and should always segfault in a deterministic manner upon stack overflow.