-
Notifications
You must be signed in to change notification settings - Fork 398
Fix unchecked memory access for files #1022
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
Changes from 7 commits
260b463
d7967f6
122549f
06ef77b
d0b4407
56c5e53
f910ea1
4bbaa72
10b9373
6e37f2f
edd0157
4baef71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| use std::collections::HashMap; | ||
| use std::fs::{File, OpenOptions, remove_file}; | ||
| use std::fs::{remove_file, File, OpenOptions}; | ||
| use std::io::{Read, Write}; | ||
|
|
||
| use rustc::ty::layout::Size; | ||
|
|
@@ -125,8 +125,11 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx | |
| // `FD_CLOEXEC` value without checking if the flag is set for the file because `std` | ||
| // always sets this flag when opening a file. However we still need to check that the | ||
| // file itself is open. | ||
| let fd_cloexec = this.eval_libc_i32("FD_CLOEXEC")?; | ||
| this.get_handle_and(fd, |_| Ok(fd_cloexec)) | ||
| if this.machine.file_handler.handles.contains_key(&fd) { | ||
| Ok(this.eval_libc_i32("FD_CLOEXEC")?) | ||
| } else { | ||
| this.handle_not_found() | ||
| } | ||
| } else { | ||
| throw_unsup_format!("The {:#x} command is not supported for `fcntl`)", cmd); | ||
| } | ||
|
|
@@ -139,9 +142,17 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx | |
|
|
||
| let fd = this.read_scalar(fd_op)?.to_i32()?; | ||
|
|
||
| this.remove_handle_and(fd, |handle, this| { | ||
| this.try_unwrap_io_result(handle.file.sync_all().map(|_| 0i32)) | ||
| }) | ||
| if let Some(handle) = this.machine.file_handler.handles.remove(&fd) { | ||
| // `File::sync_all` does the checks that are done when closing a file. We do this to | ||
| // to handle possible errors correctly. | ||
| let result = this.try_unwrap_io_result(handle.file.sync_all().map(|_| 0i32)); | ||
| // Now we actually close the file. | ||
| drop(handle); | ||
| // And return the result. | ||
| result | ||
| } else { | ||
| this.handle_not_found() | ||
| } | ||
| } | ||
|
|
||
| fn read( | ||
|
|
@@ -160,20 +171,31 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx | |
| return Ok(0); | ||
| } | ||
| let fd = this.read_scalar(fd_op)?.to_i32()?; | ||
| let buf_scalar = this.read_scalar(buf_op)?.not_undef()?; | ||
|
|
||
| // Remove the file handle to avoid borrowing issues. | ||
| this.remove_handle_and(fd, |mut handle, this| { | ||
| // Don't use `?` to avoid returning before reinserting the handle. | ||
| let bytes = this.force_ptr(buf_scalar).and_then(|buf| { | ||
| this.memory | ||
| .get_mut(buf.alloc_id)? | ||
| .get_bytes_mut(&*this.tcx, buf, Size::from_bytes(count)) | ||
| .map(|buffer| handle.file.read(buffer)) | ||
| }); | ||
| this.machine.file_handler.handles.insert(fd, handle).unwrap_none(); | ||
| this.try_unwrap_io_result(bytes?.map(|bytes| bytes as i64)) | ||
| }) | ||
| let buf = this.read_scalar(buf_op)?.not_undef()?; | ||
|
|
||
| if let Some(handle) = this.machine.file_handler.handles.get_mut(&fd) { | ||
| let count = helpers::try_into_host_usize(count) | ||
| .ok_or_else(|| err_unsup_format!("Program tries to read into buffer too big for this host platform"))?; | ||
| // We want to read at most `count` bytes | ||
| let mut bytes = vec![0; count]; | ||
| let result = handle.file.read(&mut bytes); | ||
|
|
||
| match result { | ||
| Ok(c) => { | ||
| let read_bytes = helpers::try_from_host_usize::<i64>(c) | ||
| .ok_or_else(|| err_unsup_format!("Number of read bytes {} cannot be transformed to i64", c))?; | ||
|
||
| // If reading to `bytes` did not fail, we write those bytes to the buffer. | ||
| this.memory.write_bytes(buf, bytes)?; | ||
| Ok(read_bytes) | ||
RalfJung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
| Err(e) => { | ||
| this.set_last_error_from_io_error(e)?; | ||
| Ok(-1) | ||
| } | ||
| } | ||
| } else { | ||
| this.handle_not_found() | ||
| } | ||
| } | ||
|
|
||
| fn write( | ||
|
|
@@ -192,20 +214,26 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx | |
| return Ok(0); | ||
| } | ||
| let fd = this.read_scalar(fd_op)?.to_i32()?; | ||
| let buf = this.force_ptr(this.read_scalar(buf_op)?.not_undef()?)?; | ||
|
|
||
| this.remove_handle_and(fd, |mut handle, this| { | ||
| let bytes = this.memory.get(buf.alloc_id).and_then(|alloc| { | ||
| alloc | ||
| .get_bytes(&*this.tcx, buf, Size::from_bytes(count)) | ||
| .map(|bytes| handle.file.write(bytes).map(|bytes| bytes as i64)) | ||
| }); | ||
| this.machine.file_handler.handles.insert(fd, handle).unwrap_none(); | ||
| this.try_unwrap_io_result(bytes?) | ||
| }) | ||
| let buf = this.read_scalar(buf_op)?.not_undef()?; | ||
|
|
||
| if let Some(handle) = this.machine.file_handler.handles.get_mut(&fd) { | ||
| let bytes = this.memory.read_bytes(buf, Size::from_bytes(count))?; | ||
| let result = handle.file.write(&bytes); | ||
|
|
||
| match result { | ||
| Ok(c) => helpers::try_from_host_usize::<i64>(c) | ||
| .ok_or_else(|| err_unsup_format!("Number of written bytes {} cannot be transformed to i64", c).into()), | ||
pvdrz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Err(e) => { | ||
| this.set_last_error_from_io_error(e)?; | ||
| Ok(-1) | ||
| } | ||
| } | ||
| } else { | ||
| this.handle_not_found() | ||
| } | ||
| } | ||
|
|
||
| fn unlink( &mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { | ||
| fn unlink(&mut self, path_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { | ||
| let this = self.eval_context_mut(); | ||
|
|
||
| this.check_no_isolation("unlink")?; | ||
|
|
@@ -217,49 +245,14 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx | |
| this.try_unwrap_io_result(result) | ||
| } | ||
|
|
||
| /// Helper function that gets a `FileHandle` immutable reference and allows to manipulate it | ||
| /// using the `f` closure. | ||
| /// | ||
| /// If the `fd` file descriptor does not correspond to a file, this functions returns `Ok(-1)` | ||
| /// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor). | ||
| /// | ||
| /// This function uses `T: From<i32>` instead of `i32` directly because some IO related | ||
| /// functions return different integer types (like `read`, that returns an `i64`). | ||
| fn get_handle_and<F, T: From<i32>>(&mut self, fd: i32, f: F) -> InterpResult<'tcx, T> | ||
| where | ||
| F: Fn(&FileHandle) -> InterpResult<'tcx, T>, | ||
| { | ||
| /// Function used when a handle is not found inside `FileHandler`. It returns `Ok(-1)`and sets | ||
| /// the last OS error to `libc::EBADF` (invalid file descriptor). This function uses | ||
| /// `T: From<i32>` instead of `i32` directly because some fs functions return different integer | ||
| /// types (like `read`, that returns an `i64`). | ||
| fn handle_not_found<T: From<i32>>(&mut self) -> InterpResult<'tcx, T> { | ||
| let this = self.eval_context_mut(); | ||
| if let Some(handle) = this.machine.file_handler.handles.get(&fd) { | ||
| f(handle) | ||
| } else { | ||
| let ebadf = this.eval_libc("EBADF")?; | ||
| this.set_last_error(ebadf)?; | ||
| Ok((-1).into()) | ||
| } | ||
| } | ||
|
|
||
| /// Helper function that removes a `FileHandle` and allows to manipulate it using the `f` | ||
| /// closure. This function is quite useful when you need to modify a `FileHandle` but you need | ||
| /// to modify `MiriEvalContext` at the same time, so you can modify the handle and reinsert it | ||
| /// using `f`. | ||
| /// | ||
| /// If the `fd` file descriptor does not correspond to a file, this functions returns `Ok(-1)` | ||
| /// and sets `Evaluator::last_error` to `libc::EBADF` (invalid file descriptor). | ||
| /// | ||
| /// This function uses `T: From<i32>` instead of `i32` directly because some IO related | ||
| /// functions return different integer types (like `read`, that returns an `i64`). | ||
| fn remove_handle_and<F, T: From<i32>>(&mut self, fd: i32, mut f: F) -> InterpResult<'tcx, T> | ||
| where | ||
| F: FnMut(FileHandle, &mut MiriEvalContext<'mir, 'tcx>) -> InterpResult<'tcx, T>, | ||
| { | ||
| let this = self.eval_context_mut(); | ||
| if let Some(handle) = this.machine.file_handler.handles.remove(&fd) { | ||
| f(handle, this) | ||
| } else { | ||
| let ebadf = this.eval_libc("EBADF")?; | ||
| this.set_last_error(ebadf)?; | ||
| Ok((-1).into()) | ||
| } | ||
| let ebadf = this.eval_libc("EBADF")?; | ||
| this.set_last_error(ebadf)?; | ||
| Ok((-1).into()) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.