Skip to content

ABI code generates oversize loads when returning with a cast #45543

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
arielb1 opened this issue Oct 26, 2017 · 10 comments
Open

ABI code generates oversize loads when returning with a cast #45543

arielb1 opened this issue Oct 26, 2017 · 10 comments
Assignees
Labels
A-codegen Area: Code generation A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. I-crash Issue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics. P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@arielb1
Copy link
Contributor

arielb1 commented Oct 26, 2017

Meta

$ rustc --version
rustc 1.22.0-nightly (185cc5f26 2017-10-02)

STR

#![crate_type="rlib"]

#[no_mangle]
pub extern "C" fn foo(x: &(i8, i8, i8)) -> (i8, i8, i8) {
    *x
}

target is x86_64-unknown-linux-gnu

Expected Result

Code generated should not have UB

Actual Result

The generated no-opt LLVM IR is:

; ModuleID = 'cast.cgu-0.rs'
source_filename = "cast.cgu-0.rs"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

; Function Attrs: nounwind uwtable
define i32 @foo({ i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }* noalias readonly dereferenceable(3)) unnamed_addr #0 {
start:
  %_3 = alloca { i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }
  ; COMMENT MINE: %_0 has size 3
  %_0 = alloca { i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }
  %1 = bitcast { i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }* %0 to i8*
  %2 = bitcast { i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }* %_3 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %2, i8* %1, i64 3, i32 1, i1 false)
  %3 = bitcast { i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }* %_3 to i8*
  %4 = bitcast { i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }* %_0 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %4, i8* %3, i64 3, i32 1, i1 false)
  ; COMMENT MINE it is cast to an i32 here
  %5 = bitcast { i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }* %_0 to i32*
  ; COMMENT MINE and then loaded as through it was size 4
  %6 = load i32, i32* %5, align 1
  ret i32 %6
}

; Function Attrs: argmemonly nounwind
declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) #1

attributes #0 = { nounwind uwtable "probe-stack"="__rust_probestack" }
attributes #1 = { argmemonly nounwind }

This loads 4 bytes of an alloca of size 3. I couldn't trivially turn this to an end-to-end mis-code-generation (this can't be used to load from anything but the return pointer, so you can't do the standard "load at end of page" trick), but I can't see how can oversize loads can be anything but UB.

cc @eddyb - I think one solution would be to pad the return pointer alloca to always have enough space for the cast, aka:

  %_0_store = alloca {{ i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }, [1 x i8]}
  %_0 = getelementptr inbounds {{ i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }, [1 x i8]}, {{ i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }, [1 x i8]}* %_0_store, i32 0, i32 1

I think the problem load is the one in

let load = bcx.load(
, but I can't be sure.

@arielb1 arielb1 added A-codegen Area: Code generation A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. I-nominated T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 26, 2017
@arielb1
Copy link
Contributor Author

arielb1 commented Oct 26, 2017

This also happens when casting arguments - currently arguments can be "arbitrary" temporaries, so the return value fix won't work, but this still can't directly be used for the "end of page" segfaults:

#![crate_type="rlib"]

#[repr(C)]
#[derive(Copy, Clone)]
pub struct U24(u8, u8, u8);

extern "C" {
    fn bar(x: U24) -> U24;
}

pub extern "C" fn foo(x: U24) -> U24 {
    unsafe { bar(x) }
}

generates:

; ModuleID = 'cast2.cgu-0.rs'
source_filename = "cast2.cgu-0.rs"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

%U24 = type { i8, [0 x i8], i8, [0 x i8], i8, [0 x i8] }

; cast2::foo
; Function Attrs: nounwind uwtable
define i32 @_ZN5cast23foo17h1f3d5ac3e0c9cc02E(i32) unnamed_addr #0 {
start:
  %abi_cast1 = alloca i32
  %_3 = alloca %U24
  %x = alloca %U24
  %_0 = alloca %U24
  %abi_cast = alloca i32
  %arg0 = alloca %U24
  store i32 %0, i32* %abi_cast
  %1 = bitcast %U24* %arg0 to i8*
  %2 = bitcast i32* %abi_cast to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %1, i8* %2, i64 3, i32 1, i1 false)
  %3 = bitcast %U24* %arg0 to i8*
  %4 = bitcast %U24* %x to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %4, i8* %3, i64 3, i32 1, i1 false)
  %5 = bitcast %U24* %x to i8*
  %6 = bitcast %U24* %_3 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %6, i8* %5, i64 3, i32 1, i1 false)
  %7 = bitcast %U24* %_3 to i32*
  %8 = load i32, i32* %7, align 1 ; bad load #1
  %9 = call i32 @bar(i32 %8)
  store i32 %9, i32* %abi_cast1
  %10 = bitcast %U24* %_0 to i8*
  %11 = bitcast i32* %abi_cast1 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* %10, i8* %11, i64 3, i32 1, i1 false)
  br label %bb1

bb1:                                              ; preds = %start
  %12 = bitcast %U24* %_0 to i32*
  %13 = load i32, i32* %12, align 1 ; bad load #2
  ret i32 %13
}

; Function Attrs: argmemonly nounwind
declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) #1

; Function Attrs: nounwind
declare i32 @bar(i32) unnamed_addr #2

attributes #0 = { nounwind uwtable "probe-stack"="__rust_probestack" }
attributes #1 = { argmemonly nounwind }
attributes #2 = { nounwind "probe-stack"="__rust_probestack" }

@arielb1
Copy link
Contributor Author

arielb1 commented Oct 26, 2017

The example above makes doing the alloca oversize less of a good idea - we eventually want to allow passing references to struct fields directly as function parameters, where we do not want to add a "guard byte". I think the "i24" case (which I think also includes 5/6/7-byte fields) should just be handled by doing an ugly memcpy.

@eddyb
Copy link
Member

eddyb commented Oct 26, 2017

I remember fixing this, I don't understand how we're still having issues like these.

@arielb1
Copy link
Contributor Author

arielb1 commented Oct 26, 2017

@eddyb

We fixed this for stores, where the problems were obvious (stack corruption), to actually get a problem with a load is harder - you have to engage an LLVM optimization.

@TimNN TimNN added the C-bug Category: This is a bug. label Oct 31, 2017
@nikomatsakis
Copy link
Contributor

triage: P-medium

@rust-highfive rust-highfive added P-medium Medium priority and removed I-nominated labels Nov 2, 2017
@arielb1 arielb1 added P-high High priority and removed P-medium Medium priority labels Nov 2, 2017
@dotdash
Copy link
Contributor

dotdash commented Dec 7, 2017

According to #29988 (comment) this was fixed once.

@nikomatsakis nikomatsakis added the I-crash Issue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics. label Jan 4, 2018
@dotdash dotdash self-assigned this Jan 11, 2018
@nikomatsakis
Copy link
Contributor

Question: Do we feel this needs to stay P-high? This hasn't yet cropped up in the wild as far as I know, but it'd be good to get it done so that it never does.

@dotdash, think you'll have time to investigate?

@dotdash
Copy link
Contributor

dotdash commented Jan 18, 2018 via email

dotdash added a commit to dotdash/rust that referenced this issue Feb 8, 2018
The x86_64 SysV ABI should use exact sizes for small structs passed in
registers, i.e. a struct that occupies 3 bytes should use an i24,
instead of the i32 it currently uses.

Refs rust-lang#45543
bors added a commit that referenced this issue Feb 11, 2018
Fix oversized loads on x86_64 SysV FFI calls

The x86_64 SysV ABI should use exact sizes for small structs passed in
registers, i.e. a struct that occupies 3 bytes should use an i24,
instead of the i32 it currently uses.

Refs #45543
bors added a commit that referenced this issue Feb 11, 2018
Fix oversized loads on x86_64 SysV FFI calls

The x86_64 SysV ABI should use exact sizes for small structs passed in
registers, i.e. a struct that occupies 3 bytes should use an i24,
instead of the i32 it currently uses.

Refs #45543
@nikomatsakis
Copy link
Contributor

triage: P-medium

We do not know of an instance of this occurring in the wild. It is fixed on x86_64 (right @dotdash?). Seems like it's not P-high. But @dotdash if you are able to close completely, that would be awesome!

@rust-highfive rust-highfive added P-medium Medium priority and removed P-high High priority labels Feb 15, 2018
@eddyb eddyb removed their assignment Feb 23, 2018
@steveklabnik
Copy link
Member

Triage: don't have a good way to test this out, since I don't really know anything about sparc64.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
A-codegen Area: Code generation A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. C-bug Category: This is a bug. I-crash Issue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics. P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants