Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
# qemu: true
- host_target: aarch64-apple-darwin
os: macos-latest
- host_target: i686-pc-windows-msvc
- host_target: x86_64-pc-windows-msvc
os: windows-latest
- host_target: aarch64-pc-windows-msvc
os: windows-11-arm
Expand Down
9 changes: 4 additions & 5 deletions ci/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ case $HOST_TARGET in
i686-unknown-linux-gnu)
# Host
MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
# Not officially supported tier 2
# Not officially supported tier 2. Also covers "64bit target on 32bit host".
MANY_SEEDS=16 TEST_TARGET=aarch64-linux-android run_tests
MANY_SEEDS=16 TEST_TARGET=loongarch64-unknown-linux-gnu run_tests
# Partially supported targets (no_std, tier 2)
Expand All @@ -175,7 +175,7 @@ case $HOST_TARGET in
MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
# Custom target JSON file
TEST_TARGET=tests/x86_64-unknown-kernel.json MIRI_NO_STD=1 MIRIFLAGS="-Zunstable-options" run_tests_minimal no_std
# Not officially supported tier 2
# Not officially supported tier 2.
MANY_SEEDS=16 TEST_TARGET=x86_64-pc-solaris run_tests
MANY_SEEDS=16 TEST_TARGET=mips-unknown-linux-gnu run_tests # a 32bit big-endian target, and also a target without 64bit atomics
MANY_SEEDS=16 TEST_TARGET=riscv64a23-unknown-linux-gnu run_tests
Expand All @@ -187,12 +187,11 @@ case $HOST_TARGET in
MANY_SEEDS=64 TEST_TARGET=i686-pc-windows-gnu run_tests
MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-msvc CARGO_MIRI_ENV=1 run_tests
;;
i686-pc-windows-msvc)
x86_64-pc-windows-msvc)
# Host
MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 run_tests
# Extra tier 1
# We really want to ensure a Linux target works on a Windows host,
# and a 64bit target works on a 32bit host.
# We really want to ensure a Linux target works on a Windows host.
TEST_TARGET=x86_64-unknown-linux-gnu run_tests
;;
aarch64-pc-windows-msvc)
Expand Down
2 changes: 1 addition & 1 deletion rust-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
f248f4038796913873f11ca65b1b901e311c8dae
315ecf4a939def16631c2b25c3782ad67fc22160
61 changes: 48 additions & 13 deletions src/shims/sig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,19 @@ macro_rules! shim_sig_arg {
$this.tcx.types.bool
};
($this:ident, *_) => {
// Pointee types usually don't matter so we allow it to be omitted.
// Mutability does not matter for ABI.
$this.machine.layouts.mut_raw_ptr.ty
};
($this:ident, *$($ty:tt)*) => {
// Pointee types matter for varargs so we support explicitly giving them.
// Mutability does not matter for ABI.
rustc_middle::ty::Ty::new_ptr(
*$this.tcx,
shim_sig_arg!($this, $($ty)*),
rustc_middle::mir::Mutability::Mut,
)
};
($this:ident, fn(..) -> _) => {
// We currently treat fn ptrs as ABI-compatible with data ptrs so we can just use a raw ptr.
$this.machine.layouts.const_raw_ptr.ty
Expand Down Expand Up @@ -208,12 +218,12 @@ fn check_shim_abi<'tcx>(

if caller_abi.c_variadic && !callee_abi.c_variadic {
throw_ub_format!(
"ABI mismatch: `{link_name}` is a non-variadic function, but the caller is using a variadic signature"
"ABI mismatch: `{link_name}` is a non-variadic function, but the caller is using a c-variadic signature"
);
}
if !caller_abi.c_variadic && callee_abi.c_variadic {
throw_ub_format!(
"ABI mismatch: `{link_name}` is a variadic function, but the caller is using a non-variadic signature"
"ABI mismatch: `{link_name}` is a c-variadic function, but the caller is using a non-variadic signature"
);
}

Expand Down Expand Up @@ -254,7 +264,7 @@ fn check_shim_abi<'tcx>(
// Deliberately not `Copy` so that we don't consume the same vararg multiple times accidentally.
pub struct Varargs<'tcx, 'a> {
args: &'a [OpTy<'tcx>],
/// Number of variadic arguments that have already been taken, for error messages.
/// Number of arguments (variadic and fixed) that have already been taken, for error messages.
already_gone: usize,
}

Expand Down Expand Up @@ -299,7 +309,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
}
if abi.c_variadic {
throw_ub_format!(
"calling a non-variadic function with a variadic caller-side signature"
"calling a non-variadic function with a c-variadic caller-side signature"
);
}

Expand Down Expand Up @@ -360,7 +370,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {

// Return arguments.
if let Some((fixed, var)) = caller_args.split_first_chunk() {
return interp_ok((fixed, Varargs { args: var, already_gone: 0 }));
return interp_ok((fixed, Varargs { args: var, already_gone: N }));
}
unreachable!()
}
Expand All @@ -378,20 +388,45 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {

let Some((now, tail)) = varargs.args.split_first_chunk::<N>() else {
throw_ub_format!(
"not enough variadic arguments for `{fn_name}`: got {}, expected at least {}",
"not enough arguments for `{fn_name}`: got {}, expected at least {}",
varargs.already_gone.strict_add(varargs.args.len()),
varargs.already_gone.strict_add(N),
)
};

for (n, (caller_gave, callee_expected)) in now.iter().zip(tys).enumerate() {
// Check ABI compatibility. This is less strict than `next_arg` but we're also
// not limited to just a few simple types.
let callee_expected = this.layout_of(callee_expected)?;

// FIXME: check compatibility once <https://github.com/rust-lang/rust/pull/161615>
// landed.
let _unused = (n, caller_gave, callee_expected);
// Check ABI compatibility.
let compatible =
this.validate_c_variadic_compatible_ty(caller_gave.layout.ty, callee_expected)?;
match compatible {
VarArgCompatible::Compatible => {}
VarArgCompatible::Incompatible => {
throw_ub_format!(
"incorrect c-variadic argument type for `{fn_name}`: \
expected argument #{n} to have type `{callee_expected}` but got incompatible type `{caller_ty}`",
n = varargs.already_gone.strict_add(n).strict_add(1),
caller_ty = caller_gave.layout.ty,
);
}
VarArgCompatible::CastIntTo { source_is_signed } => {
// Check that the value can be represented in the target type.
let size = caller_gave.layout.size;
let scalar = this.read_scalar(caller_gave)?;
if scalar.to_int(size)? < 0 {
throw_ub_format!(
"incorrect c-variadic argument type for `{fn_name}`: \
argument #{n} has value `{value}_{caller_ty}` which cannot be represented in expected type `{callee_expected}`",
n = varargs.already_gone.strict_add(n).strict_add(1),
caller_ty = caller_gave.layout.ty,
value = if source_is_signed {
scalar.to_int(size)?.to_string()
} else {
scalar.to_uint(size)?.to_string()
}
)
}
}
}
}

interp_ok((now, Varargs { args: tail, already_gone: varargs.already_gone.strict_add(N) }))
Expand Down
13 changes: 9 additions & 4 deletions src/shims/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,11 +460,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
let o_creat = this.eval_libc_i32("O_CREAT");
if flag & o_creat == o_creat {
flag &= !o_creat;
// Get the mode. On macOS, the argument type `mode_t` is actually `u16`, but
// C integer promotion rules mean that on the ABI level, it gets passed as `u32`
// (see https://github.com/rust-lang/rust/issues/71915).
// Get the mode.
let ([mode], _) = this.check_varargs(
shim_varargs![libc::mode_t],
if this.libc_ty_layout("mode_t").size.bytes() >= 4 {
// `mode_t` is big enough, no C integer promotion.
shim_varargs![libc::mode_t]
} else {
// Types smaller than int get promoted to int
// (see https://github.com/rust-lang/rust/issues/71915).
shim_varargs![i32]
},
varargs,
"open(pathname, O_CREAT, ...)",
)?;
Expand Down
13 changes: 8 additions & 5 deletions src/shims/unix/linux_like/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub fn futex<'tcx>(
dest: &MPlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let ([addr, op, val], varargs) =
ecx.check_varargs(shim_varargs![*_, i32, u32], varargs, "syscall(SYS_futex, ...)")?;
ecx.check_varargs(shim_varargs![*u32, i32, u32], varargs, "syscall(SYS_futex, ...)")?;

// See <https://man7.org/linux/man-pages/man2/futex.2.html> for docs.
// The first three arguments (after the syscall number itself) are the same to all futex operations:
Expand Down Expand Up @@ -52,7 +52,7 @@ pub fn futex<'tcx>(

let (timeout, bitset) = if wait_bitset {
let ([timeout, uaddr2, bitset], _) = ecx.check_varargs(
shim_varargs![*_, *_, u32],
shim_varargs![*libc::timespec, *u32, u32],
varargs,
"syscall(SYS_futex, ...)",
)?;
Expand All @@ -62,8 +62,11 @@ pub fn futex<'tcx>(
}
(timeout, ecx.read_scalar(bitset)?.to_u32()?)
} else {
let ([timeout], _) =
ecx.check_varargs(shim_varargs![*_], varargs, "syscall(SYS_futex, ...)")?;
let ([timeout], _) = ecx.check_varargs(
shim_varargs![*libc::timespec],
varargs,
"syscall(SYS_futex, ...)",
)?;
(timeout, u32::MAX)
};

Expand Down Expand Up @@ -199,7 +202,7 @@ pub fn futex<'tcx>(

let bitset = if op == futex_wake_bitset {
let ([timeout, uaddr2, bitset], _) = ecx.check_varargs(
shim_varargs![*_, *_, u32],
shim_varargs![*libc::timespec, *u32, u32],
varargs,
"syscall(SYS_futex, ...)",
)?;
Expand Down
4 changes: 2 additions & 2 deletions src/shims/unix/linux_like/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub fn syscall<'tcx>(
// Used by getrandom 0.1
// The first argument is the syscall id, so skip over it.
let ([ptr, len, flags], _) = ecx.check_varargs(
shim_varargs![*_, usize, i32],
shim_varargs![*libc::c_void, usize, i32],
varargs,
"syscall(SYS_getrandom, ...)",
)?;
Expand Down Expand Up @@ -70,7 +70,7 @@ pub fn syscall<'tcx>(
num if num == sys_accept4 => {
// Used on Android.
let ([socket, address, address_len, flags], _) = ecx.check_varargs(
shim_varargs![i32, *_, *_, i32],
shim_varargs![i32, *libc::sockaddr, *libc::socklen_t, i32],
varargs,
"syscall(SYS_accept4, ...)",
)?;
Expand Down
14 changes: 10 additions & 4 deletions src/shims/unix/linux_like/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ pub fn prctl<'tcx>(

let res = match ecx.read_scalar(op)?.to_i32()? {
op if op == pr_set_name => {
let ([name], _) =
ecx.check_varargs(shim_varargs![*_], varargs, "prctl(PR_SET_NAME, ...)")?;
let ([name], _) = ecx.check_varargs(
shim_varargs![*libc::c_char],
varargs,
"prctl(PR_SET_NAME, ...)",
)?;

let name = ecx.read_scalar(name)?;
let thread = ecx.pthread_self()?;
Expand All @@ -38,8 +41,11 @@ pub fn prctl<'tcx>(
Scalar::from_u32(0)
}
op if op == pr_get_name => {
let ([name], _) =
ecx.check_varargs(shim_varargs![*_], varargs, "prctl(PR_GET_NAME, ...)")?;
let ([name], _) = ecx.check_varargs(
shim_varargs![*libc::c_char],
varargs,
"prctl(PR_GET_NAME, ...)",
)?;

let name = ecx.read_scalar(name)?;
let thread = ecx.pthread_self()?;
Expand Down
2 changes: 1 addition & 1 deletion src/shims/unix/tcp_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ impl UnixFileDescription for TcpSocket {
);
}

let ([value_ptr], _) = ecx.check_varargs(shim_varargs![*_], args, "ioctl")?;
let ([value_ptr], _) = ecx.check_varargs(shim_varargs![*i32], args, "ioctl")?;
let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
self.is_non_block.set(non_block);
Expand Down
2 changes: 1 addition & 1 deletion src/shims/unix/virtual_socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ impl UnixFileDescription for VirtualSocket {
);
}

let ([value_ptr], _) = ecx.check_varargs(shim_varargs![*_], args, "ioctl")?;
let ([value_ptr], _) = ecx.check_varargs(shim_varargs![*i32], args, "ioctl")?;
let value = ecx.deref_pointer_as(value_ptr, ecx.machine.layouts.i32)?;
let non_block = ecx.read_scalar(&value)?.to_i32()? != 0;
self.is_nonblock.set(non_block);
Expand Down
2 changes: 1 addition & 1 deletion tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ fn main() {

fn test_file_open_missing_needed_mode() {
let name = c"missing_arg.txt".as_ptr();
let _fd = unsafe { libc::open(name, libc::O_CREAT) }; //~ ERROR: Undefined Behavior: not enough variadic arguments
let _fd = unsafe { libc::open(name, libc::O_CREAT) }; //~ ERROR: /Undefined Behavior: not enough arguments.*: got 2/
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
error: Undefined Behavior: not enough variadic arguments for `open(pathname, O_CREAT, ...)`: got 0, expected at least 1
error: Undefined Behavior: not enough arguments for `open(pathname, O_CREAT, ...)`: got 2, expected at least 3
--> tests/fail-dep/libc/fs/unix_open_missing_required_mode.rs:LL:CC
|
LL | let _fd = unsafe { libc::open(name, libc::O_CREAT) };
Expand Down
18 changes: 18 additions & 0 deletions tests/fail-dep/libc/open_wrong_var_arg_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//@ignore-target: windows # File handling is not implemented yet
//@compile-flags: -Zmiri-disable-isolation
//@normalize-stderr-test: "to have type `(u32|i32)`" -> "to have type `$$TYPE`"

#![allow(invalid_runtime_symbol_definitions)]

use std::ffi::{CString, OsStr};
use std::os::unix::ffi::OsStrExt;

use libc::open;

fn main() {
let c_path = CString::new(OsStr::new("./text").as_bytes()).expect("CString::new failed");
let _fd = unsafe {
open(c_path.as_ptr(), libc::O_CREAT, /* should be mode_t */ 0u64)
//~^ ERROR: /expected argument #3 to have type `(u32|i32)` but got incompatible type `u64`/
};
}
13 changes: 13 additions & 0 deletions tests/fail-dep/libc/open_wrong_var_arg_type.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
error: Undefined Behavior: incorrect c-variadic argument type for `open(pathname, O_CREAT, ...)`: expected argument #3 to have type `$TYPE` but got incompatible type `u64`
--> tests/fail-dep/libc/open_wrong_var_arg_type.rs:LL:CC
|
LL | open(c_path.as_ptr(), libc::O_CREAT, /* should be mode_t */ 0u64)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

23 changes: 23 additions & 0 deletions tests/fail/function_calls/not_the_same_c_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
mod somewhere_else {
#[repr(C)]
struct CType {
field: i32,
}

#[unsafe(no_mangle)]
extern "C" fn work_with_c_type(_x: CType) {}
//~^ERROR: parameter #1 has type somewhere_else::CType passing argument of type CType
}

// Imagine we import the function from above but we end up with a copy of the type declaration.
// We only accept this if the types are *exactly* the name, including the names of all fields.
#[repr(C)]
struct CType(i32);

extern "C" {
fn work_with_c_type(_x: CType);
}

fn main() {
unsafe { work_with_c_type(CType(0)) };
}
20 changes: 20 additions & 0 deletions tests/fail/function_calls/not_the_same_c_type.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
error: Undefined Behavior: calling a function whose parameter #1 has type somewhere_else::CType passing argument of type CType
--> tests/fail/function_calls/not_the_same_c_type.rs:LL:CC
|
LL | extern "C" fn work_with_c_type(_x: CType) {}
| ^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
= help: if you think this code should be accepted anyway, please report an issue with Miri
= note: stack backtrace:
0: somewhere_else::work_with_c_type
at tests/fail/function_calls/not_the_same_c_type.rs:LL:CC
1: main
at tests/fail/function_calls/not_the_same_c_type.rs:LL:CC

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ fn main() {
let c_path = CString::new(OsStr::new("./text").as_bytes()).expect("CString::new failed");
let _fd = unsafe {
open(c_path.as_ptr(), /* value does not matter */ 0)
//~^ ERROR: is a variadic function, but the caller is using a non-variadic signature
//~^ ERROR: is a c-variadic function, but the caller is using a non-variadic signature
};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: Undefined Behavior: ABI mismatch: `open` is a variadic function, but the caller is using a non-variadic signature
--> tests/fail/shims/non_vararg_signature_mismatch.rs:LL:CC
error: Undefined Behavior: ABI mismatch: `open` is a c-variadic function, but the caller is using a non-variadic signature
--> tests/fail/shims/vararg_callee_signature_mismatch.rs:LL:CC
|
LL | open(c_path.as_ptr(), /* value does not matter */ 0)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
Expand Down
Loading
Loading