-
Notifications
You must be signed in to change notification settings - Fork 13.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Locals aligned to greater than page size can cause unsound behavior #70143
Comments
This is most likely an LLVM bug. |
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
ret In 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
ret Here 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
retq Again, 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 (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
…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 387e2c2)
…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
Visiting for P-high review. In the absence of a fix for this issue, I'd like to see a diagnostic warning people who have locals with alignment > page-size that they might hit this. rust-lang/rust-clippy#8593 seems relevant to that. Beyond that, my main question on #70143 is whether we can find an owner to try to implement the fix that's described. It sounds like @cuviper has already done a fair amount of experimentation. I don't know if they have capacity to own moving further with it. |
Updating my earlier godbolt to current nightly (https://rust.godbolt.org/z/hseenMTKP) still shows the same asm, still just as problematic. However, I also rechecked (NB: inline-asm won't apply for Windows and its |
Even /ALIGN:4096 will still lead to a crash in Debug mode. The following issues might be relevant: rust-lang/rust#70022 rust-lang/rust#70143
I think we can now consider this a Windows-only bug, because the rest are now using @rustbot label -O-linux -O-windows-msvc +O-windows |
Visited during T-compiler P-high review: as far as we can tell this still needs someone to try and investigate/fix from the llvm side. Labeled with |
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(pnkfelix edit: see comment thread, its a more general problem.)pc-windows-msvc
The text was updated successfully, but these errors were encountered: