Locals aligned to greater than page size can cause unsound behavior #70143
Comments
|
This is most likely an LLVM bug. |
Limit maximum alignment in #[repr(align)] to 4K Let's try this on crater to see the impact. cc rust-lang#70022 rust-lang#70143 rust-lang#70144
Limit maximum alignment in #[repr(align)] to 4K Let's try this on crater to see the impact. cc rust-lang#70022 rust-lang#70143 rust-lang#70144
Limit maximum alignment in #[repr(align)] to 4K Let's try this on crater to see the impact. cc rust-lang#70022 rust-lang#70143 rust-lang#70144
|
Could someone who knows the technical details here report this bug upstream with LLVM? |
|
@rustbot ping llvm |
|
Hey LLVM ICE-breakers! This bug has been identified as a good cc @camelid @comex @cuviper @DutchGhost @dyncat @hanna-kruppe @hdhoang @heyrutvik @JOE1994 @jryans @mmilenko @nagisa @nikic @Noah-Kennedy @SiavoshZarrasvand @spastorino @vertexclique |
|
I think this is platform-specific but it still all happens to varying degrees on x86(-64). This safe code causes a stack overflow on Ubuntu: #[repr(align(0x800000))]
struct Aligned(usize);
fn main() {
let x = Aligned(2);
println!("{}", x.0);
}This safe code causes a segfault on Ubuntu: #[repr(align(0x10000000))]
struct Aligned(usize);
fn main() {
let x = Aligned(2);
println!("{}", x.0);
}This isn't specific to Windows, then, and can be used to cause stack overflows and/or segfaults in safe code. I don't know how this would be done, but maybe doing such alignments should only be allowed if |
|
Note that we do install a stack guard and associated handler to check for stack overflows. So just getting a stack overflow or segfault does not necessarily demonstrate a problem. Rust cannot statically make sure that your stack is always big enough, so being able to trigger a stack overflow through big locals or deep recursion is entirely expected behavior. The bug here is about the locals not having the alignment that was requested via |
The locals do absolutely have the required alignment. The issue is with the stack aligning prologue which can cause the function's frame to exist entirely past the guard page so unrelated memory can be stomped on. |
|
I just tried this on godbolt: https://rust.godbolt.org/z/8xfn4v #[repr(align(0x10000000))]
struct Aligned(usize);
pub fn foo() -> usize {
Aligned(2).0
}The output for example::foo:
push rbp
mov eax, 536870896 # 0x1ffffff0
call __chkstk
sub rsp, rax
lea rbp, [rsp + 128]
and rsp, -268435456 # 0xfffffffff0000000
mov qword ptr [rsp], 2
mov rax, qword ptr [rsp]
lea rsp, [rbp + 536870768] # 0x1fffff70
pop rbp
retIn the worst case, the The output for example::foo:
push rbp
mov rbp, rsp
and rsp, -268435456 # 0xfffffffff0000000
mov eax, 536870912 # 0x20000000
call __rust_probestack
sub rsp, rax
mov qword ptr [rsp], 2
mov rax, qword ptr [rsp]
mov rsp, rbp
pop rbp
retHere the alignment is done first, but it's still bad. We might start with Here's one more Linux test I ran locally, using LLVM 11's example::foo:
pushq %rbp
movq %rsp, %rbp
andq $-268435456, %rsp # 0xfffffffff0000000
movq %rsp, %r11
subq $536870912, %r11 # 0x20000000
.LBB0_1:
subq $4096, %rsp
movq $0, (%rsp)
cmpq %r11, %rsp
jne .LBB0_1
movq $2, (%rsp)
movq (%rsp), %rax
movq %rbp, %rsp
popq %rbp
retqAgain, the initial alignment could jump the guard page, then the probe loop may be clobbering non-stack memory. |
|
It seems that the ideal behavior here would be to add the alignment to the size passed to |
|
Yeah, I've been talking privately with Serge, and he's going to try implementing something like that. |
|
I thought this is what already happens?
And the fix would be to align first and then run the stack probe? |
The fix is to first probe the stack for the frame size plus the alignment, and then align the stack. Currently stack probes only check the frame size without adding the alignment to that size. |
|
Note that the size of the alignment adjustment is a dynamic value -- the offset from whatever incoming stack pointer we get to an aligned pointer below that. Then the actual frame size is allocated after we have known alignment. If the stack guard page is anywhere in that total range, we need to fault, which is what the probing does. |
|
I guess I am wondering why we can't first align and then probe for the already computed new stack size, that sounds like it avoids doing the aligning twice. But it probably doesn't work for other reasons I cannot see. |
|
@RalfJung Because after aligning the current stack pointer can be past the guard page in a completely unrelated section of memory, that could be completely valid memory for the full size of the stack frame that the stack probe could succeed on, but it's not our stack and we'd be trampling that unrelated memory! We don't need to do the aligning twice as we can do a simple conservative stack probe of frame size + alignment, which is always sound, but it can trigger stack overflows when there is still a bit of space left. A smarter combination of stack probing + aligning can also avoid computing the aligned address twice, but might involve more implementation effort. Regardless of what technique we use, this only matters for functions with locals that are aligned to a page size or greater. |
|
Ah I see, I somehow assumed stack probing would still have access to the old top-of-stack and could thus probe all the way from there to the aligned stack with the new frame. |
|
The examples I gave do have the old pointer in |
…otection As reported in rust-lang/rust#70143 alignment is not taken into account when doing the probing. Fix that by adjusting the first probe if the stack align is small, or by extending the dynamic probing if the alignment is large. Differential Revision: https://reviews.llvm.org/D84419
…otection As reported in rust-lang/rust#70143 alignment is not taken into account when doing the probing. Fix that by adjusting the first probe if the stack align is small, or by extending the dynamic probing if the alignment is large. Differential Revision: https://reviews.llvm.org/D84419
…otection As reported in rust-lang/rust#70143 alignment is not taken into account when doing the probing. Fix that by adjusting the first probe if the stack align is small, or by extending the dynamic probing if the alignment is large. Differential Revision: https://reviews.llvm.org/D84419
…otection As reported in rust-lang/rust#70143 alignment is not taken into account when doing the probing. Fix that by adjusting the first probe if the stack align is small, or by extending the dynamic probing if the alignment is large. Differential Revision: https://reviews.llvm.org/D84419 (cherry picked from commit f2c6bfa)
…otection As reported in rust-lang/rust#70143 alignment is not taken into account when doing the probing. Fix that by adjusting the first probe if the stack align is small, or by extending the dynamic probing if the alignment is large. Differential Revision: https://reviews.llvm.org/D84419 (cherry picked from commit f2c6bfa)
…otection As reported in rust-lang/rust#70143 alignment is not taken into account when doing the probing. Fix that by adjusting the first probe if the stack align is small, or by extending the dynamic probing if the alignment is large. Differential Revision: https://reviews.llvm.org/D84419 (cherry picked from commit f2c6bfa)
…otection As reported in rust-lang/rust#70143 alignment is not taken into account when doing the probing. Fix that by adjusting the first probe if the stack align is small, or by extending the dynamic probing if the alignment is large. Differential Revision: https://reviews.llvm.org/D84419
Forked from #70022
Minimal example
Aligning the stack is done after the stack probe. Because stacks grow downwards and aligning the stack shifts it downwards, it can cause the end of the stack to extend past the guard page and cause invalid access exceptions or worse when those sections of the stack are touched.
Only confirmed that this occurs on
pc-windows-msvc.The text was updated successfully, but these errors were encountered: