Skip to content

Commit 09b0a8a

Browse files
committed
Auto merge of #1022 - christianpoveda:fix-fd-access, r=RalfJung
Fix unchecked memory access for files This PR takes care of two problems: - It uses `Memory::(read|write)_bytes` to guarantee that memory accesses are checked (Fixes: #1007) - It removes the `(get|remove)_handle_and` methods which were a little bit cumbersome to use. In particular `remove_handle_and`, because we were using it to avoid borrowing issues before the `Evaluator::memory` field was public. @RalfJung @oli-obk
2 parents 69415b4 + 4baef71 commit 09b0a8a

File tree

1 file changed

+84
-79
lines changed

1 file changed

+84
-79
lines changed

src/shims/fs.rs

Lines changed: 84 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::HashMap;
2-
use std::fs::{File, OpenOptions, remove_file};
2+
use std::convert::TryFrom;
3+
use std::fs::{remove_file, File, OpenOptions};
34
use std::io::{Read, Write};
45

56
use rustc::ty::layout::Size;
@@ -125,8 +126,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
125126
// `FD_CLOEXEC` value without checking if the flag is set for the file because `std`
126127
// always sets this flag when opening a file. However we still need to check that the
127128
// file itself is open.
128-
let fd_cloexec = this.eval_libc_i32("FD_CLOEXEC")?;
129-
this.get_handle_and(fd, |_| Ok(fd_cloexec))
129+
if this.machine.file_handler.handles.contains_key(&fd) {
130+
Ok(this.eval_libc_i32("FD_CLOEXEC")?)
131+
} else {
132+
this.handle_not_found()
133+
}
130134
} else {
131135
throw_unsup_format!("The {:#x} command is not supported for `fcntl`)", cmd);
132136
}
@@ -139,9 +143,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
139143

140144
let fd = this.read_scalar(fd_op)?.to_i32()?;
141145

142-
this.remove_handle_and(fd, |handle, this| {
143-
this.try_unwrap_io_result(handle.file.sync_all().map(|_| 0i32))
144-
})
146+
if let Some(handle) = this.machine.file_handler.handles.remove(&fd) {
147+
// `File::sync_all` does the checks that are done when closing a file. We do this to
148+
// to handle possible errors correctly.
149+
let result = this.try_unwrap_io_result(handle.file.sync_all().map(|_| 0i32));
150+
// Now we actually close the file.
151+
drop(handle);
152+
// And return the result.
153+
result
154+
} else {
155+
this.handle_not_found()
156+
}
145157
}
146158

147159
fn read(
@@ -154,27 +166,50 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
154166

155167
this.check_no_isolation("read")?;
156168

157-
let count = this.read_scalar(count_op)?.to_machine_usize(&*this.tcx)?;
169+
let ptr_size = this.pointer_size().bits();
170+
171+
// We cap the number of read bytes to the largest value that we are able to fit in both the
172+
// host's and target's `isize`.
173+
let count = this
174+
.read_scalar(count_op)?
175+
.to_machine_usize(&*this.tcx)?
176+
.min((1 << (ptr_size - 1)) - 1) // max value of target `isize`
177+
.min(isize::max_value() as u64);
158178
// Reading zero bytes should not change `buf`.
159179
if count == 0 {
160180
return Ok(0);
161181
}
162182
let fd = this.read_scalar(fd_op)?.to_i32()?;
163-
let buf_scalar = this.read_scalar(buf_op)?.not_undef()?;
164-
165-
// Remove the file handle to avoid borrowing issues.
166-
this.remove_handle_and(fd, |mut handle, this| {
167-
// Don't use `?` to avoid returning before reinserting the handle.
168-
let bytes = this.force_ptr(buf_scalar).and_then(|buf| {
169-
// FIXME: Don't use raw methods
170-
this.memory
171-
.get_raw_mut(buf.alloc_id)?
172-
.get_bytes_mut(&*this.tcx, buf, Size::from_bytes(count))
173-
.map(|buffer| handle.file.read(buffer))
174-
});
175-
this.machine.file_handler.handles.insert(fd, handle).unwrap_none();
176-
this.try_unwrap_io_result(bytes?.map(|bytes| bytes as i64))
177-
})
183+
let buf = this.read_scalar(buf_op)?.not_undef()?;
184+
185+
if let Some(handle) = this.machine.file_handler.handles.get_mut(&fd) {
186+
// This can never fail because `count` was capped to be smaller than
187+
// `isize::max_value()`.
188+
let count = isize::try_from(count).unwrap();
189+
// We want to read at most `count` bytes. We are sure that `count` is not negative
190+
// because it was a target's `usize`. Also we are sure that its smaller than
191+
// `usize::max_value()` because it is a host's `isize`.
192+
let mut bytes = vec![0; count as usize];
193+
let result = handle
194+
.file
195+
.read(&mut bytes)
196+
// `File::read` never returns a value larger than `count`, so this cannot fail.
197+
.map(|c| i64::try_from(c).unwrap());
198+
199+
match result {
200+
Ok(read_bytes) => {
201+
// If reading to `bytes` did not fail, we write those bytes to the buffer.
202+
this.memory.write_bytes(buf, bytes)?;
203+
Ok(read_bytes)
204+
}
205+
Err(e) => {
206+
this.set_last_error_from_io_error(e)?;
207+
Ok(-1)
208+
}
209+
}
210+
} else {
211+
this.handle_not_found()
212+
}
178213
}
179214

180215
fn write(
@@ -187,27 +222,32 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
187222

188223
this.check_no_isolation("write")?;
189224

190-
let count = this.read_scalar(count_op)?.to_machine_usize(&*this.tcx)?;
225+
let ptr_size = this.pointer_size().bits();
226+
227+
// We cap the number of read bytes to the largest value that we are able to fit in both the
228+
// host's and target's `isize`.
229+
let count = this
230+
.read_scalar(count_op)?
231+
.to_machine_usize(&*this.tcx)?
232+
.min((1 << (ptr_size - 1)) - 1) // max value of target `isize`
233+
.min(isize::max_value() as u64);
191234
// Writing zero bytes should not change `buf`.
192235
if count == 0 {
193236
return Ok(0);
194237
}
195238
let fd = this.read_scalar(fd_op)?.to_i32()?;
196-
let buf = this.force_ptr(this.read_scalar(buf_op)?.not_undef()?)?;
197-
198-
this.remove_handle_and(fd, |mut handle, this| {
199-
// FIXME: Don't use raw methods
200-
let bytes = this.memory.get_raw(buf.alloc_id).and_then(|alloc| {
201-
alloc
202-
.get_bytes(&*this.tcx, buf, Size::from_bytes(count))
203-
.map(|bytes| handle.file.write(bytes).map(|bytes| bytes as i64))
204-
});
205-
this.machine.file_handler.handles.insert(fd, handle).unwrap_none();
206-
this.try_unwrap_io_result(bytes?)
207-
})
239+
let buf = this.read_scalar(buf_op)?.not_undef()?;
240+
241+
if let Some(handle) = this.machine.file_handler.handles.get_mut(&fd) {
242+
let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?;
243+
let result = handle.file.write(&bytes).map(|c| i64::try_from(c).unwrap());
244+
this.try_unwrap_io_result(result)
245+
} else {
246+
this.handle_not_found()
247+
}
208248
}
209249

210-
fn unlink( &mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
250+
fn unlink(&mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> {
211251
let this = self.eval_context_mut();
212252

213253
this.check_no_isolation("unlink")?;
@@ -219,49 +259,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
219259
this.try_unwrap_io_result(result)
220260
}
221261

222-
/// Helper function that gets a `FileHandle` immutable reference and allows to manipulate it
223-
/// using the `f` closure.
224-
///
225-
/// If the `fd` file descriptor does not correspond to a file, this functions returns `Ok(-1)`
226-
/// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor).
227-
///
228-
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
229-
/// functions return different integer types (like `read`, that returns an `i64`).
230-
fn get_handle_and<F, T: From<i32>>(&mut self, fd: i32, f: F) -> InterpResult<'tcx, T>
231-
where
232-
F: Fn(&FileHandle) -> InterpResult<'tcx, T>,
233-
{
262+
/// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets
263+
/// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses
264+
/// `T: From<i32>` instead of `i32` directly because some fs functions return different integer
265+
/// types (like `read`, that returns an `i64`).
266+
fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> {
234267
let this = self.eval_context_mut();
235-
if let Some(handle) = this.machine.file_handler.handles.get(&fd) {
236-
f(handle)
237-
} else {
238-
let ebadf = this.eval_libc("EBADF")?;
239-
this.set_last_error(ebadf)?;
240-
Ok((-1).into())
241-
}
242-
}
243-
244-
/// Helper function that removes a `FileHandle` and allows to manipulate it using the `f`
245-
/// closure. This function is quite useful when you need to modify a `FileHandle` but you need
246-
/// to modify `MiriEvalContext` at the same time, so you can modify the handle and reinsert it
247-
/// using `f`.
248-
///
249-
/// If the `fd` file descriptor does not correspond to a file, this functions returns `Ok(-1)`
250-
/// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor).
251-
///
252-
/// This function uses `T: From<i32>` instead of `i32` directly because some IO related
253-
/// functions return different integer types (like `read`, that returns an `i64`).
254-
fn remove_handle_and<F, T: From<i32>>(&mut self, fd: i32, mut f: F) -> InterpResult<'tcx, T>
255-
where
256-
F: FnMut(FileHandle, &mut MiriEvalContext<'mir, 'tcx>) -> InterpResult<'tcx, T>,
257-
{
258-
let this = self.eval_context_mut();
259-
if let Some(handle) = this.machine.file_handler.handles.remove(&fd) {
260-
f(handle, this)
261-
} else {
262-
let ebadf = this.eval_libc("EBADF")?;
263-
this.set_last_error(ebadf)?;
264-
Ok((-1).into())
265-
}
268+
let ebadf = this.eval_libc("EBADF")?;
269+
this.set_last_error(ebadf)?;
270+
Ok((-1).into())
266271
}
267272
}

0 commit comments

Comments
 (0)