Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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‎

Lines changed: 6 additions & 0 deletions
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‎

Lines changed: 46 additions & 12 deletions
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-
pubstructIO_STATUS_BLOCK {
320-
pubPointer:*mutc_void,
321-
pubInformation:usize,
322+
unionIO_STATUS_BLOCK_union {
323+
Status:NTSTATUS,
324+
Pointer:*mutc_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‎

Lines changed: 103 additions & 66 deletions
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)

AltStyle によって変換されたページ (->オリジナル) /