diff --git a/tokio/src/fs/write.rs b/tokio/src/fs/write.rs index 8ee4ead50..bf7fbd195 100644 --- a/tokio/src/fs/write.rs +++ b/tokio/src/fs/write.rs @@ -56,7 +56,15 @@ async fn write_uring(path: &Path, contents: OwnedBuf) -> io::Result<()> { let mut pos = 0; let mut buf = contents.as_ref(); while !buf.is_empty() { - let n = Op::write_at(fd, buf, pos)?.await? as usize; + // SAFETY: + // If the operation completes successfully, `fd` and `buf` are still + // alive within the scope of this function, so remain valid. + // + // In the case of cancellation, local variables within the scope of + // this `async fn` are dropped in the reverse order of their declaration. + // Therefore, `Op` is dropped before `fd` and `buf`, ensuring that the + // operation finishes gracefully before these resources are dropped. + let n = unsafe { Op::write_at(fd, buf, pos) }?.await? as usize; if n == 0 { return Err(io::ErrorKind::WriteZero.into()); } diff --git a/tokio/src/io/uring/write.rs b/tokio/src/io/uring/write.rs index 334473435..95e4dce91 100644 --- a/tokio/src/io/uring/write.rs +++ b/tokio/src/io/uring/write.rs @@ -23,7 +23,12 @@ impl Cancellable for Write { } impl Op { - pub(crate) fn write_at(fd: BorrowedFd<'_>, buf: &[u8], offset: u64) -> io::Result { + /// # SAFETY + /// + /// The caller must ensure that `fd` and `buf` remain valid until the + /// operation finishes (or gets cancelled) and the `Op::drop` completes. + /// Otherwise, the kernel could access freed memory, breaking soundness. + pub(crate) unsafe fn write_at(fd: BorrowedFd<'_>, buf: &[u8], offset: u64) -> io::Result { // There is a cap on how many bytes we can write in a single uring write operation. // ref: https://github.com/axboe/liburing/discussions/497 let len: u32 = cmp::min(buf.len(), u32::MAX as usize) as u32; @@ -32,8 +37,8 @@ impl Op { .offset(offset) .build(); - // SAFETY: `fd` and `buf` are owned by caller function, ensuring these params are - // valid until operation completes. + // SAFETY: `fd` and `buf` valid until the operation completes or gets cancelled + // and the `Op::drop` completes. let op = unsafe { Op::new(sqe, Write) }; Ok(op) }