Skip to content

Commit 26b5e0c

Browse files
committed
Auto merge of #95469 - ChrisDenton:unsound-read-write, r=joshtriplett
Fix unsound `File` methods This is a draft attempt to fix #81357. *EDIT*: this PR now tackles `read()`, `write()`, `read_at()`, `write_at()` and `read_buf`. Still needs more testing though. cc `@jstarks,` can you confirm the the Windows team is ok with the Rust stdlib using `NtReadFile` and `NtWriteFile`? ~Also, I'm provisionally using `CancelIo` in a last ditch attempt to recover but I'm not sure that this is actually a good idea. Especially as getting into this state would be a programmer error so aborting the process is justified in any case.~ *EDIT*: removed, see comments.
2 parents bbe9d27 + d2ce150 commit 26b5e0c

File tree

3 files changed

+155
-78
lines changed

3 files changed

+155
-78
lines changed

library/std/src/fs.rs

+6
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ use crate::time::SystemTime;
8585
/// by different processes. Avoid assuming that holding a `&File` means that the
8686
/// file will not change.
8787
///
88+
/// # Platform-specific behavior
89+
///
90+
/// On Windows, the implementation of [`Read`] and [`Write`] traits for `File`
91+
/// perform synchronous I/O operations. Therefore the underlying file must not
92+
/// have been opened for asynchronous I/O (e.g. by using `FILE_FLAG_OVERLAPPED`).
93+
///
8894
/// [`BufReader<R>`]: io::BufReader
8995
/// [`sync_all`]: File::sync_all
9096
#[stable(feature = "rust1", since = "1.0.0")]

library/std/src/sys/windows/c.rs

+46-12
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ pub const STATUS_SUCCESS: NTSTATUS = 0x00000000;
274274
pub const STATUS_DELETE_PENDING: NTSTATUS = 0xc0000056_u32 as _;
275275
pub const STATUS_INVALID_PARAMETER: NTSTATUS = 0xc000000d_u32 as _;
276276

277+
pub const STATUS_PENDING: NTSTATUS = 0x103 as _;
278+
pub const STATUS_END_OF_FILE: NTSTATUS = 0xC0000011_u32 as _;
279+
277280
// Equivalent to the `NT_SUCCESS` C preprocessor macro.
278281
// See: https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/using-ntstatus-values
279282
pub fn nt_success(status: NTSTATUS) -> bool {
@@ -316,22 +319,34 @@ impl Default for OBJECT_ATTRIBUTES {
316319
}
317320
}
318321
#[repr(C)]
319-
pub struct IO_STATUS_BLOCK {
320-
pub Pointer: *mut c_void,
321-
pub Information: usize,
322+
union IO_STATUS_BLOCK_union {
323+
Status: NTSTATUS,
324+
Pointer: *mut c_void,
322325
}
323-
impl Default for IO_STATUS_BLOCK {
326+
impl Default for IO_STATUS_BLOCK_union {
324327
fn default() -> Self {
325-
Self { Pointer: ptr::null_mut(), Information: 0 }
328+
Self { Pointer: ptr::null_mut() }
326329
}
327330
}
331+
#[repr(C)]
332+
#[derive(Default)]
333+
pub struct IO_STATUS_BLOCK {
334+
u: IO_STATUS_BLOCK_union,
335+
pub Information: usize,
336+
}
328337

329338
pub type LPOVERLAPPED_COMPLETION_ROUTINE = unsafe extern "system" fn(
330339
dwErrorCode: DWORD,
331340
dwNumberOfBytesTransfered: DWORD,
332341
lpOverlapped: *mut OVERLAPPED,
333342
);
334343

344+
type IO_APC_ROUTINE = unsafe extern "system" fn(
345+
ApcContext: *mut c_void,
346+
IoStatusBlock: *mut IO_STATUS_BLOCK,
347+
Reserved: ULONG,
348+
);
349+
335350
#[repr(C)]
336351
#[cfg(not(target_pointer_width = "64"))]
337352
pub struct WSADATA {
@@ -971,13 +986,6 @@ extern "system" {
971986
lpOverlapped: LPOVERLAPPED,
972987
lpCompletionRoutine: LPOVERLAPPED_COMPLETION_ROUTINE,
973988
) -> BOOL;
974-
pub fn WriteFile(
975-
hFile: BorrowedHandle<'_>,
976-
lpBuffer: LPVOID,
977-
nNumberOfBytesToWrite: DWORD,
978-
lpNumberOfBytesWritten: LPDWORD,
979-
lpOverlapped: LPOVERLAPPED,
980-
) -> BOOL;
981989
pub fn WriteFileEx(
982990
hFile: BorrowedHandle<'_>,
983991
lpBuffer: LPVOID,
@@ -1268,6 +1276,32 @@ compat_fn! {
12681276
) -> NTSTATUS {
12691277
panic!("`NtCreateFile` not available");
12701278
}
1279+
pub fn NtReadFile(
1280+
FileHandle: BorrowedHandle<'_>,
1281+
Event: HANDLE,
1282+
ApcRoutine: Option<IO_APC_ROUTINE>,
1283+
ApcContext: *mut c_void,
1284+
IoStatusBlock: &mut IO_STATUS_BLOCK,
1285+
Buffer: *mut crate::mem::MaybeUninit<u8>,
1286+
Length: ULONG,
1287+
ByteOffset: Option<&LARGE_INTEGER>,
1288+
Key: Option<&ULONG>
1289+
) -> NTSTATUS {
1290+
panic!("`NtReadFile` not available");
1291+
}
1292+
pub fn NtWriteFile(
1293+
FileHandle: BorrowedHandle<'_>,
1294+
Event: HANDLE,
1295+
ApcRoutine: Option<IO_APC_ROUTINE>,
1296+
ApcContext: *mut c_void,
1297+
IoStatusBlock: &mut IO_STATUS_BLOCK,
1298+
Buffer: *const u8,
1299+
Length: ULONG,
1300+
ByteOffset: Option<&LARGE_INTEGER>,
1301+
Key: Option<&ULONG>
1302+
) -> NTSTATUS {
1303+
panic!("`NtWriteFile` not available");
1304+
}
12711305
pub fn RtlNtStatusToDosError(
12721306
Status: NTSTATUS
12731307
) -> ULONG {

library/std/src/sys/windows/handle.rs

+103-66
Original file line numberDiff line numberDiff line change
@@ -74,20 +74,10 @@ impl FromRawHandle for Handle {
7474

7575
impl Handle {
7676
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
77-
let mut read = 0;
78-
let len = cmp::min(buf.len(), <c::DWORD>::MAX as usize) as c::DWORD;
79-
let res = cvt(unsafe {
80-
c::ReadFile(
81-
self.as_handle(),
82-
buf.as_mut_ptr() as c::LPVOID,
83-
len,
84-
&mut read,
85-
ptr::null_mut(),
86-
)
87-
});
77+
let res = unsafe { self.synchronous_read(buf.as_mut_ptr().cast(), buf.len(), None) };
8878

8979
match res {
90-
Ok(_) => Ok(read as usize),
80+
Ok(read) => Ok(read as usize),
9181

9282
// The special treatment of BrokenPipe is to deal with Windows
9383
// pipe semantics, which yields this error when *reading* from
@@ -109,42 +99,23 @@ impl Handle {
10999
}
110100

111101
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
112-
let mut read = 0;
113-
let len = cmp::min(buf.len(), <c::DWORD>::MAX as usize) as c::DWORD;
114-
let res = unsafe {
115-
let mut overlapped: c::OVERLAPPED = mem::zeroed();
116-
overlapped.Offset = offset as u32;
117-
overlapped.OffsetHigh = (offset >> 32) as u32;
118-
cvt(c::ReadFile(
119-
self.as_handle(),
120-
buf.as_mut_ptr() as c::LPVOID,
121-
len,
122-
&mut read,
123-
&mut overlapped,
124-
))
125-
};
102+
let res =
103+
unsafe { self.synchronous_read(buf.as_mut_ptr().cast(), buf.len(), Some(offset)) };
104+
126105
match res {
127-
Ok(_) => Ok(read as usize),
106+
Ok(read) => Ok(read as usize),
128107
Err(ref e) if e.raw_os_error() == Some(c::ERROR_HANDLE_EOF as i32) => Ok(0),
129108
Err(e) => Err(e),
130109
}
131110
}
132111

133112
pub fn read_buf(&self, buf: &mut ReadBuf<'_>) -> io::Result<()> {
134-
let mut read = 0;
135-
let len = cmp::min(buf.remaining(), <c::DWORD>::MAX as usize) as c::DWORD;
136-
let res = cvt(unsafe {
137-
c::ReadFile(
138-
self.as_handle(),
139-
buf.unfilled_mut().as_mut_ptr() as c::LPVOID,
140-
len,
141-
&mut read,
142-
ptr::null_mut(),
143-
)
144-
});
113+
let res = unsafe {
114+
self.synchronous_read(buf.unfilled_mut().as_mut_ptr(), buf.remaining(), None)
115+
};
145116

146117
match res {
147-
Ok(_) => {
118+
Ok(read) => {
148119
// Safety: `read` bytes were written to the initialized portion of the buffer
149120
unsafe {
150121
buf.assume_init(read as usize);
@@ -221,18 +192,7 @@ impl Handle {
221192
}
222193

223194
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
224-
let mut amt = 0;
225-
let len = cmp::min(buf.len(), <c::DWORD>::MAX as usize) as c::DWORD;
226-
cvt(unsafe {
227-
c::WriteFile(
228-
self.as_handle(),
229-
buf.as_ptr() as c::LPVOID,
230-
len,
231-
&mut amt,
232-
ptr::null_mut(),
233-
)
234-
})?;
235-
Ok(amt as usize)
195+
self.synchronous_write(&buf, None)
236196
}
237197

238198
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
@@ -245,21 +205,7 @@ impl Handle {
245205
}
246206

247207
pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
248-
let mut written = 0;
249-
let len = cmp::min(buf.len(), <c::DWORD>::MAX as usize) as c::DWORD;
250-
unsafe {
251-
let mut overlapped: c::OVERLAPPED = mem::zeroed();
252-
overlapped.Offset = offset as u32;
253-
overlapped.OffsetHigh = (offset >> 32) as u32;
254-
cvt(c::WriteFile(
255-
self.as_handle(),
256-
buf.as_ptr() as c::LPVOID,
257-
len,
258-
&mut written,
259-
&mut overlapped,
260-
))?;
261-
}
262-
Ok(written as usize)
208+
self.synchronous_write(&buf, Some(offset))
263209
}
264210

265211
pub fn try_clone(&self) -> io::Result<Self> {
@@ -274,6 +220,97 @@ impl Handle {
274220
) -> io::Result<Self> {
275221
Ok(Self(self.0.duplicate(access, inherit, options)?))
276222
}
223+
224+
/// Performs a synchronous read.
225+
///
226+
/// If the handle is opened for asynchronous I/O then this abort the process.
227+
/// See #81357.
228+
///
229+
/// If `offset` is `None` then the current file position is used.
230+
unsafe fn synchronous_read(
231+
&self,
232+
buf: *mut mem::MaybeUninit<u8>,
233+
len: usize,
234+
offset: Option<u64>,
235+
) -> io::Result<usize> {
236+
let mut io_status = c::IO_STATUS_BLOCK::default();
237+
238+
// The length is clamped at u32::MAX.
239+
let len = cmp::min(len, c::DWORD::MAX as usize) as c::DWORD;
240+
let status = c::NtReadFile(
241+
self.as_handle(),
242+
ptr::null_mut(),
243+
None,
244+
ptr::null_mut(),
245+
&mut io_status,
246+
buf,
247+
len,
248+
offset.map(|n| n as _).as_ref(),
249+
None,
250+
);
251+
match status {
252+
// If the operation has not completed then abort the process.
253+
// Doing otherwise means that the buffer and stack may be written to
254+
// after this function returns.
255+
c::STATUS_PENDING => {
256+
eprintln!("I/O error: operation failed to complete synchronously");
257+
crate::process::abort();
258+
}
259+
260+
// Return `Ok(0)` when there's nothing more to read.
261+
c::STATUS_END_OF_FILE => Ok(0),
262+
263+
// Success!
264+
status if c::nt_success(status) => Ok(io_status.Information),
265+
266+
status => {
267+
let error = c::RtlNtStatusToDosError(status);
268+
Err(io::Error::from_raw_os_error(error as _))
269+
}
270+
}
271+
}
272+
273+
/// Performs a synchronous write.
274+
///
275+
/// If the handle is opened for asynchronous I/O then this abort the process.
276+
/// See #81357.
277+
///
278+
/// If `offset` is `None` then the current file position is used.
279+
fn synchronous_write(&self, buf: &[u8], offset: Option<u64>) -> io::Result<usize> {
280+
let mut io_status = c::IO_STATUS_BLOCK::default();
281+
282+
// The length is clamped at u32::MAX.
283+
let len = cmp::min(buf.len(), c::DWORD::MAX as usize) as c::DWORD;
284+
let status = unsafe {
285+
c::NtWriteFile(
286+
self.as_handle(),
287+
ptr::null_mut(),
288+
None,
289+
ptr::null_mut(),
290+
&mut io_status,
291+
buf.as_ptr(),
292+
len,
293+
offset.map(|n| n as _).as_ref(),
294+
None,
295+
)
296+
};
297+
match status {
298+
// If the operation has not completed then abort the process.
299+
// Doing otherwise means that the buffer may be read and the stack
300+
// written to after this function returns.
301+
c::STATUS_PENDING => {
302+
rtabort!("I/O error: operation failed to complete synchronously");
303+
}
304+
305+
// Success!
306+
status if c::nt_success(status) => Ok(io_status.Information),
307+
308+
status => {
309+
let error = unsafe { c::RtlNtStatusToDosError(status) };
310+
Err(io::Error::from_raw_os_error(error as _))
311+
}
312+
}
313+
}
277314
}
278315

279316
impl<'a> Read for &'a Handle {

0 commit comments

Comments
 (0)