Skip to content

Commit 96195a5

Browse files
authored
Rollup merge of rust-lang#130342 - RalfJung:slice-idx-overflow, r=saethlin
interpret, miri: fix dealing with overflow during slice indexing and allocation This is mostly to fix rust-lang#130284. I then realized we're using somewhat sketchy arguments for a similar multiplication in `copy`/`copy_nonoverlapping`/`write_bytes`, so I made them all share the same function that checks exactly the right thing. (The intrinsics would previously fail on allocations larger than `1 << 47` bytes... which are theoretically possible maybe? Anyway it seems conceptually wrong to use any other bound than `isize::MAX` here.)
2 parents 18a93ca + 268f6cf commit 96195a5

File tree

11 files changed

+109
-45
lines changed

11 files changed

+109
-45
lines changed

Diff for: compiler/rustc_const_eval/src/interpret/intrinsics.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
216216
self.copy_intrinsic(&args[0], &args[1], &args[2], /*nonoverlapping*/ false)?;
217217
}
218218
sym::write_bytes => {
219-
self.write_bytes_intrinsic(&args[0], &args[1], &args[2])?;
219+
self.write_bytes_intrinsic(&args[0], &args[1], &args[2], "write_bytes")?;
220220
}
221221
sym::compare_bytes => {
222222
let result = self.compare_bytes_intrinsic(&args[0], &args[1], &args[2])?;
@@ -599,9 +599,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
599599
let count = self.read_target_usize(count)?;
600600
let layout = self.layout_of(src.layout.ty.builtin_deref(true).unwrap())?;
601601
let (size, align) = (layout.size, layout.align.abi);
602-
// `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
603-
// but no actual allocation can be big enough for the difference to be noticeable.
604-
let size = size.checked_mul(count, self).ok_or_else(|| {
602+
603+
let size = self.compute_size_in_bytes(size, count).ok_or_else(|| {
605604
err_ub_custom!(
606605
fluent::const_eval_size_overflow,
607606
name = if nonoverlapping { "copy_nonoverlapping" } else { "copy" }
@@ -635,11 +634,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
635634
Ok(())
636635
}
637636

638-
pub(crate) fn write_bytes_intrinsic(
637+
pub fn write_bytes_intrinsic(
639638
&mut self,
640639
dst: &OpTy<'tcx, <M as Machine<'tcx>>::Provenance>,
641640
byte: &OpTy<'tcx, <M as Machine<'tcx>>::Provenance>,
642641
count: &OpTy<'tcx, <M as Machine<'tcx>>::Provenance>,
642+
name: &'static str,
643643
) -> InterpResult<'tcx> {
644644
let layout = self.layout_of(dst.layout.ty.builtin_deref(true).unwrap())?;
645645

@@ -649,9 +649,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
649649

650650
// `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
651651
// but no actual allocation can be big enough for the difference to be noticeable.
652-
let len = layout.size.checked_mul(count, self).ok_or_else(|| {
653-
err_ub_custom!(fluent::const_eval_size_overflow, name = "write_bytes")
654-
})?;
652+
let len = self
653+
.compute_size_in_bytes(layout.size, count)
654+
.ok_or_else(|| err_ub_custom!(fluent::const_eval_size_overflow, name = name))?;
655655

656656
let bytes = std::iter::repeat(byte).take(len.bytes_usize());
657657
self.write_bytes_ptr(dst, bytes)

Diff for: compiler/rustc_const_eval/src/interpret/memory.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
222222
} else {
223223
Allocation::try_uninit(size, align)?
224224
};
225-
self.allocate_raw_ptr(alloc, kind)
225+
self.insert_allocation(alloc, kind)
226226
}
227227

228228
pub fn allocate_bytes_ptr(
@@ -233,14 +233,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
233233
mutability: Mutability,
234234
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
235235
let alloc = Allocation::from_bytes(bytes, align, mutability);
236-
self.allocate_raw_ptr(alloc, kind)
236+
self.insert_allocation(alloc, kind)
237237
}
238238

239-
pub fn allocate_raw_ptr(
239+
pub fn insert_allocation(
240240
&mut self,
241241
alloc: Allocation<M::Provenance, (), M::Bytes>,
242242
kind: MemoryKind<M::MemoryKind>,
243243
) -> InterpResult<'tcx, Pointer<M::Provenance>> {
244+
assert!(alloc.size() <= self.max_size_of_val());
244245
let id = self.tcx.reserve_alloc_id();
245246
debug_assert_ne!(
246247
Some(kind),

Diff for: compiler/rustc_const_eval/src/interpret/operator.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use either::Either;
22
use rustc_apfloat::{Float, FloatConvert};
3-
use rustc_middle::mir::interpret::{InterpResult, Scalar};
3+
use rustc_middle::mir::interpret::{InterpResult, PointerArithmetic, Scalar};
44
use rustc_middle::mir::NullOp;
55
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
66
use rustc_middle::ty::{self, FloatTy, ScalarInt, Ty};
77
use rustc_middle::{bug, mir, span_bug};
88
use rustc_span::symbol::sym;
9+
use rustc_target::abi::Size;
910
use tracing::trace;
1011

1112
use super::{throw_ub, ImmTy, InterpCx, Machine, MemPlaceMeta};
@@ -287,6 +288,20 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
287288
})
288289
}
289290

291+
/// Computes the total size of this access, `count * elem_size`,
292+
/// checking for overflow beyond isize::MAX.
293+
pub fn compute_size_in_bytes(&self, elem_size: Size, count: u64) -> Option<Size> {
294+
// `checked_mul` applies `u64` limits independent of the target pointer size... but the
295+
// subsequent check for `max_size_of_val` means we also handle 32bit targets correctly.
296+
// (We cannot use `Size::checked_mul` as that enforces `obj_size_bound` as the limit, which
297+
// would be wrong here.)
298+
elem_size
299+
.bytes()
300+
.checked_mul(count)
301+
.map(Size::from_bytes)
302+
.filter(|&total| total <= self.max_size_of_val())
303+
}
304+
290305
fn binary_ptr_op(
291306
&self,
292307
bin_op: mir::BinOp,

Diff for: compiler/rustc_const_eval/src/interpret/projection.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_target::abi::{self, Size, VariantIdx};
1717
use tracing::{debug, instrument};
1818

1919
use super::{
20-
throw_ub, throw_unsup, InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy,
20+
err_ub, throw_ub, throw_unsup, InterpCx, InterpResult, MPlaceTy, Machine, MemPlaceMeta, OpTy,
2121
Provenance, Scalar,
2222
};
2323

@@ -229,7 +229,11 @@ where
229229
// This can only be reached in ConstProp and non-rustc-MIR.
230230
throw_ub!(BoundsCheckFailed { len, index });
231231
}
232-
let offset = stride * index; // `Size` multiplication
232+
// With raw slices, `len` can be so big that this *can* overflow.
233+
let offset = self
234+
.compute_size_in_bytes(stride, index)
235+
.ok_or_else(|| err_ub!(PointerArithOverflow))?;
236+
233237
// All fields have the same layout.
234238
let field_layout = base.layout().field(self, 0);
235239
(offset, field_layout)

Diff for: src/tools/miri/src/concurrency/thread.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
900900
// This allocation will be deallocated when the thread dies, so it is not in read-only memory.
901901
alloc.mutability = Mutability::Mut;
902902
// Create a fresh allocation with this content.
903-
let ptr = this.allocate_raw_ptr(alloc, MiriMemoryKind::Tls.into())?;
903+
let ptr = this.insert_allocation(alloc, MiriMemoryKind::Tls.into())?;
904904
this.machine.threads.set_thread_local_alloc(def_id, ptr);
905905
Ok(ptr)
906906
}

Diff for: src/tools/miri/src/intrinsics/mod.rs

+2-15
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
mod atomic;
44
mod simd;
55

6-
use std::iter;
7-
86
use rand::Rng;
97
use rustc_apfloat::{Float, Round};
10-
use rustc_middle::ty::layout::LayoutOf;
118
use rustc_middle::{
129
mir,
1310
ty::{self, FloatTy},
@@ -119,19 +116,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
119116
this.copy_op(dest, &place)?;
120117
}
121118

122-
"write_bytes" | "volatile_set_memory" => {
119+
"volatile_set_memory" => {
123120
let [ptr, val_byte, count] = check_arg_count(args)?;
124-
let ty = ptr.layout.ty.builtin_deref(true).unwrap();
125-
let ty_layout = this.layout_of(ty)?;
126-
let val_byte = this.read_scalar(val_byte)?.to_u8()?;
127-
let ptr = this.read_pointer(ptr)?;
128-
let count = this.read_target_usize(count)?;
129-
// `checked_mul` enforces a too small bound (the correct one would probably be target_isize_max),
130-
// but no actual allocation can be big enough for the difference to be noticeable.
131-
let byte_count = ty_layout.size.checked_mul(count, this).ok_or_else(|| {
132-
err_ub_format!("overflow computing total size of `{intrinsic_name}`")
133-
})?;
134-
this.write_bytes_ptr(ptr, iter::repeat(val_byte).take(byte_count.bytes_usize()))?;
121+
this.write_bytes_intrinsic(ptr, val_byte, count, "volatile_set_memory")?;
135122
}
136123

137124
// Memory model / provenance manipulation

Diff for: src/tools/miri/src/shims/foreign_items.rs

+36-12
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
196196
if size == 0 {
197197
throw_ub_format!("creating allocation with size 0");
198198
}
199-
if i128::from(size) > this.tcx.data_layout.pointer_size.signed_int_max() {
199+
if size > this.max_size_of_val().bytes() {
200200
throw_ub_format!("creating an allocation larger than half the address space");
201201
}
202202
if let Err(e) = Align::from_bytes(align) {
@@ -441,19 +441,34 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
441441
"malloc" => {
442442
let [size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
443443
let size = this.read_target_usize(size)?;
444-
let res = this.malloc(size, /*zero_init:*/ false)?;
445-
this.write_pointer(res, dest)?;
444+
if size <= this.max_size_of_val().bytes() {
445+
let res = this.malloc(size, /*zero_init:*/ false)?;
446+
this.write_pointer(res, dest)?;
447+
} else {
448+
// If this does not fit in an isize, return null and, on Unix, set errno.
449+
if this.target_os_is_unix() {
450+
let einval = this.eval_libc("ENOMEM");
451+
this.set_last_error(einval)?;
452+
}
453+
this.write_null(dest)?;
454+
}
446455
}
447456
"calloc" => {
448-
let [items, len] =
457+
let [items, elem_size] =
449458
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
450459
let items = this.read_target_usize(items)?;
451-
let len = this.read_target_usize(len)?;
452-
let size = items
453-
.checked_mul(len)
454-
.ok_or_else(|| err_ub_format!("overflow during calloc size computation"))?;
455-
let res = this.malloc(size, /*zero_init:*/ true)?;
456-
this.write_pointer(res, dest)?;
460+
let elem_size = this.read_target_usize(elem_size)?;
461+
if let Some(size) = this.compute_size_in_bytes(Size::from_bytes(elem_size), items) {
462+
let res = this.malloc(size.bytes(), /*zero_init:*/ true)?;
463+
this.write_pointer(res, dest)?;
464+
} else {
465+
// On size overflow, return null and, on Unix, set errno.
466+
if this.target_os_is_unix() {
467+
let einval = this.eval_libc("ENOMEM");
468+
this.set_last_error(einval)?;
469+
}
470+
this.write_null(dest)?;
471+
}
457472
}
458473
"free" => {
459474
let [ptr] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
@@ -465,8 +480,17 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
465480
this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
466481
let old_ptr = this.read_pointer(old_ptr)?;
467482
let new_size = this.read_target_usize(new_size)?;
468-
let res = this.realloc(old_ptr, new_size)?;
469-
this.write_pointer(res, dest)?;
483+
if new_size <= this.max_size_of_val().bytes() {
484+
let res = this.realloc(old_ptr, new_size)?;
485+
this.write_pointer(res, dest)?;
486+
} else {
487+
// If this does not fit in an isize, return null and, on Unix, set errno.
488+
if this.target_os_is_unix() {
489+
let einval = this.eval_libc("ENOMEM");
490+
this.set_last_error(einval)?;
491+
}
492+
this.write_null(dest)?;
493+
}
470494
}
471495

472496
// Rust allocation

Diff for: src/tools/miri/src/shims/unix/foreign_items.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -363,14 +363,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
363363
//
364364
// Linux: https://www.unix.com/man-page/linux/3/reallocarray/
365365
// FreeBSD: https://man.freebsd.org/cgi/man.cgi?query=reallocarray
366-
match nmemb.checked_mul(size) {
366+
match this.compute_size_in_bytes(Size::from_bytes(size), nmemb) {
367367
None => {
368368
let einval = this.eval_libc("ENOMEM");
369369
this.set_last_error(einval)?;
370370
this.write_null(dest)?;
371371
}
372372
Some(len) => {
373-
let res = this.realloc(ptr, len)?;
373+
let res = this.realloc(ptr, len.bytes())?;
374374
this.write_pointer(res, dest)?;
375375
}
376376
}

Diff for: src/tools/miri/tests/pass-dep/libc/libc-mem.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ fn test_malloc() {
101101
let slice = slice::from_raw_parts(p3 as *const u8, 20);
102102
assert_eq!(&slice, &[0_u8; 20]);
103103

104+
// new size way too big (so this doesn't actually realloc).
105+
let p_too_big = libc::realloc(p3, usize::MAX);
106+
assert!(p_too_big.is_null());
107+
104108
// old size > new size
105109
let p4 = libc::realloc(p3, 10);
106110
let slice = slice::from_raw_parts(p4 as *const u8, 10);
@@ -119,9 +123,13 @@ fn test_malloc() {
119123
unsafe {
120124
let p1 = libc::realloc(ptr::null_mut(), 20);
121125
assert!(!p1.is_null());
122-
123126
libc::free(p1);
124127
}
128+
129+
unsafe {
130+
let p_too_big = libc::malloc(usize::MAX);
131+
assert!(p_too_big.is_null());
132+
}
125133
}
126134

127135
fn test_calloc() {
@@ -143,6 +151,9 @@ fn test_calloc() {
143151
let slice = slice::from_raw_parts(p4 as *const u8, 4 * 8);
144152
assert_eq!(&slice, &[0_u8; 4 * 8]);
145153
libc::free(p4);
154+
155+
let p_too_big = libc::calloc(usize::MAX / 4, 4);
156+
assert!(p_too_big.is_null());
146157
}
147158
}
148159

Diff for: tests/ui/consts/slice-index-overflow-issue-130284.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const C: () = {
2+
let value = [1, 2];
3+
let ptr = value.as_ptr().wrapping_add(2);
4+
let fat = std::ptr::slice_from_raw_parts(ptr, usize::MAX);
5+
unsafe {
6+
// This used to ICE, but it should just report UB.
7+
let _ice = (*fat)[usize::MAX - 1];
8+
//~^ERROR: constant value failed
9+
//~| overflow
10+
}
11+
};
12+
13+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
error[E0080]: evaluation of constant value failed
2+
--> $DIR/slice-index-overflow-issue-130284.rs:7:20
3+
|
4+
LL | let _ice = (*fat)[usize::MAX - 1];
5+
| ^^^^^^^^^^^^^^^^^^^^^^ overflowing pointer arithmetic: the total offset in bytes does not fit in an `isize`
6+
7+
error: aborting due to 1 previous error
8+
9+
For more information about this error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)