guard_trait/lib.rs
1//! This crate provides a guarding mechanism for memory, with an interface that is in some ways
2//! similar to `core::pin::Pin`.
3//!
4//! # Motivation
5//!
6//! What this crate attempts to solve, is the problem that data races can occur for memory that is
7//! shared with another process or the kernel (via `io_uring` for instance). If the memory is still
8//! shared when the original thread continues to execute after a system call for example, the
9//! original buffer can still be accessed while the system call allows the handler to keep using
10//! the memory. This does not happen with traditional blocking syscalls; the kernel will only
11//! access the memory during the syscall, when the process cannot temporarily do anything else.
12//!
13//! However, for more advanced asynchronous interfaces such as `io_uring` that never copy memory,
14//! the memory can still be used once the system call has started, which can lead to data races
15//! between two actors sharing memory. To prevent this data race, it is not possible to:
16//!
17//! 1) Read the memory while it is being written to by the kernel, or write to the memory while it
18//! is being read by the kernel. This is exactly like Rust's aliasing rules: we can either allow
19//! both the kernel and this process to read a buffer, for example in the system call
20//! _write(2)_, we can temporarily give the kernel exclusive ownership one or more buffers when
21//! the kernel is going to write to them, or we can avoid sharing memory at all with the kernel,
22//! __but we cannot let either actor have mutable access while the other has any access at
23//! all.__ (_aliasing invariant_)
24//! 2) Reclaim the memory while it is being read from or written to by the kernel. This is as
25//! simple as it sounds: we simply do not want the buffers to be used for other purposes, either
26//! by returning the memory to the heap, where it can be allocated simply so that the kernel can
27//! overwrite it when it is not supposed to, or it can corrupt stack variables. (_reclamation
28//! invariant_)
29//!
30//! The term "kernel" does not necessarily have to be the other actor that the memory is shared
31//! with; on Redox for example, the `io_uring` interface can work solely between regular userspace
32//! processes. Additionally, although being a somewhat niche case, this can also be used for safe
33//! wrappers protecting memory for DMA in device drivers, with a few additional restrictions
34//! (regarding cache coherency) to make that work.
35//!
36//! This buffer sharing logic does unfortunately not play very well with the current asynchronous
37//! ecosystem, where almost all I/O is done using regular borrowed slices, and references are
38//! merely borrows which are cancellable _at any time_, even by leaking. This functions perfectly
39//! when you use _synchronous_ (but non-blocking) system calls where either the process or the
40//! kernel can execute at a time. In contrast, `io_uring` is _asynchronous_, meaning that the
41//! kernel can read and write to buffers, _while our program is executing_. Therefore, a future
42//! that locally stores an array, aliased by the kernel in `io_uring`, cannot stop the kernel from
43//! using the memory again in any reasonable way, if the future were to be `Drop`ped, without
44//! blocking indefinitely. What is even worse, is that futures can be leaked at any time, and
45//! arrays allocated on the stack can also be dropped, when the memory is still in use by the
46//! kernel, as a buffer to write data from e.g. a socket. If a (mutable) buffer on the stack is
47//! then reused later for regular variables... arbitrary program corruption!
48//!
49//! What we need in order to solve these two complications, is some way to be able to mark a memory
50//! region as both "borrowed by the kernel" (mutably or immutably), and "undroppable". Since the
51//! Rust borrow checker is smart, any mutable reference with a lifetime that is shorter than
52//! `'static`, can trivially be leaked, and the pointer can be used again. This rules out any
53//! reference of lifetime `'a` that `'static` outlives, as those may be used again outside of the
54//! borrow, potentially mutably. Immutable static references are however completely harmless, since
55//! they cannot be dropped nor accessed mutably, and immutable aliasing is always permitted.
56//!
57//! Consequently, all buffers that are going to be used in safe code, must be owned. This either
58//! means heap-allocated objects (since we can assume that the heap as a whole has the `'static`
59//! lifetime, and allocations stay forever, until deallocated explicitly), buffer pools which
60//! themselves have a guarding mechanism, and static references (both mutable and immutable). We
61//! can however allow borrowed data as well, but because of the semantics around lifetimes, and the
62//! very fact that the compiler has no idea that the kernel is also involved, that requires unsafe
63//! code.
64//!
65//! Consider reading ["Mental experiments with
66//! `io_uring`"](https://vorner.github.io/2019/11/03/io-uring-mental-experiments.html), and ["Notes
67//! on `io-uring`"](https://without.boats/blog/io-uring/) for more information about these
68//! challenges.
69//!
70//! # Interface
71//!
72//! The way `guard_trait` solves this, is by adding two simple traits: `Guarded` and `GuardedMut`.
73//! `Guarded` is automatically implemented for every pointer type that implements `Deref`,
74//! `StableDeref` and `'static`. Similarly, `GuardedMut` is implemented under the same conditions,
75//! and provided that the pointer implements `DerefMut`. A consequence of this, is that nearly all
76//! owned container types, such as `Arc`, `Box`, `Vec`, etc., all implement the traits, and can
77//! thus be used with completion-based interfaces.
78//!
79//! For scenarios where it is impossible to ensure at the type level, that a certain pointer
80//! follows the guard invariants, `AssertSafe` also exists, but is unsafe to initialize.
81//!
82//! Buffers can also be mapped in a self-referencial way, similar to how `owning-ref` works, using
83//! `GuardedExt::map` and `GuardedMutExt::map_mut`. This is especially important when slice
84//! indexing is needed, as the only way to limit the number of bytes to do I/O with, generally is
85//! to shorten the slice.
86
87#![deny(broken_intra_doc_links, missing_docs)]
88#![cfg_attr(not(any(test, feature = "std")), no_std)]
89
90use core::marker::PhantomData;
91use core::ptr::NonNull;
92use core::{fmt, ops};
93
94// TODO: Perhaps consider basing everything on the Borrow trait, with StableBorrow.
95pub extern crate stable_deref_trait;
96pub use stable_deref_trait::StableDeref;
97
98/// A trait for pointer types that uphold the guard invariants, namely that the pointer must be
99/// owned, and that it must dereference into a stable address.
100pub unsafe trait Guarded {
101 /// The target pointee that this pointer may dereference into. There are no real restrictions
102 /// to what this type can be. However, the user must not assume that simply because a `&Target`
103 /// reference is protected, that references indirectly derived (via `Deref` and other traits)
104 /// would also be protected.
105 type Target: ?Sized;
106
107 /// Borrow the pointee, into a fixed reference that can be sent directly and safely to e.g.
108 /// memory-sharing completion-based I/O interfaces.
109 ///
110 /// Implementors of such interfaces must however take buffers by reference to maintain safety.
111 fn borrow_guarded(&self) -> &Self::Target;
112}
113/// A trait for pointer types that uphold the guard invariants, and are able to dereference
114/// mutably.
115pub unsafe trait GuardedMut: Guarded {
116 /// Borrow the pointee mutably, into a fixed reference that can be sent directly and safely to
117 /// e.g. memory-sharing completion-based I/O interfaces.
118 ///
119 /// Implementors of such interfaces must however take buffers by reference to maintain safety.
120 fn borrow_guarded_mut(&mut self) -> &mut Self::Target;
121}
122
123unsafe impl<T, U> Guarded for T
124where
125 T: ops::Deref<Target = U> + StableDeref + 'static,
126 U: ?Sized,
127{
128 type Target = U;
129
130 #[inline]
131 fn borrow_guarded(&self) -> &U {
132 &*self
133 }
134}
135unsafe impl<T, U> GuardedMut for T
136where
137 T: ops::DerefMut<Target = U> + StableDeref + 'static,
138 U: ?Sized,
139{
140 #[inline]
141 fn borrow_guarded_mut(&mut self) -> &mut U {
142 &mut *self
143 }
144}
145
146/// A type for pointers that cannot uphold the necessary guard invariants at the type level, but
147/// which can be assumed to behave properly by unsafe code.
148#[repr(transparent)]
149#[derive(Debug)]
150pub struct AssertSafe<T> {
151 inner: T,
152}
153
154impl<T> AssertSafe<T>
155where
156 T: ops::Deref,
157{
158 /// Wrap a general-purpose pointer into a wrapper that implements the `Guarded` (and
159 /// potentially `GuardedMut`) traits, provided that the pointer upholds this invariants anyway.
160 ///
161 /// # Safety
162 ///
163 /// For the guard invariants to be upheld, the pointer must:
164 ///
165 /// * dereference into a stable location. This forbids types that implement `Deref` by
166 /// borrowing data that they own without a heap-based container in between;
167 /// * be _owned_. Any type that has a shorter lifetime than `'static`, may have its borrow
168 /// cancelled _at any time_, with the original borrowed data accessible again.
169 #[inline]
170 pub unsafe fn new_unchecked(inner: T) -> Self {
171 Self { inner }
172 }
173}
174unsafe impl<T, U> Guarded for AssertSafe<T>
175where
176 T: ops::Deref<Target = U>,
177 U: ?Sized,
178{
179 type Target = U;
180
181 #[inline]
182 fn borrow_guarded(&self) -> &Self::Target {
183 &*self.inner
184 }
185}
186unsafe impl<T, U> GuardedMut for AssertSafe<T>
187where
188 T: ops::Deref<Target = U> + ops::DerefMut,
189 U: ?Sized,
190{
191 #[inline]
192 fn borrow_guarded_mut(&mut self) -> &mut Self::Target {
193 &mut *self.inner
194 }
195}
196
197/// A mapped guard, which contains a guarded owned pointer, and an immutable reference to that
198/// pointer. It has had a one-time closure applied to it, but only the _output_ of the closure is
199/// stored, not the closure itself. This is similar to how crates like `owning_ref` work.
200pub struct Mapped<T, U>
201where
202 T: Guarded,
203 U: ?Sized,
204{
205 // NOTE: It is important to make the distinction between the pointer and the pointee here. T is
206 // a pointer type in this context, which is guaranteed to dereference into a stable address.
207 // However, unlike with MappedMut, this can be trivially dereferenced at any time, since
208 // immutable references allow multiple aliases.
209 inner: T,
210 // NOTE: NonNull gives us a covariant non-null pointer, which is valid for immutable
211 // references. We cannot overwrite a subtype with a supertype without mutable access.
212 mapped: NonNull<U>,
213}
214impl<T, U> Mapped<T, U>
215where
216 T: Guarded,
217 U: ?Sized,
218{
219 /// Move the inner guarded pointer out from the `Mapped` wrapper, cancelling the temporary
220 /// borrow.
221 #[inline]
222 pub fn into_original(self) -> T {
223 self.inner
224 }
225 /// Get the original pointer, by immutable reference.
226 #[inline]
227 pub fn original_by_ref(&self) -> &T {
228 &self.inner
229 }
230 /// Retrieve an immutable reference to the mapped data.
231 #[inline]
232 pub fn get_ref(&self) -> &U {
233 unsafe { self.mapped.as_ref() }
234 }
235 /// Map the mapped wrapper again, converting `&U` to `&V`.
236 #[inline]
237 pub fn and_then<F, V>(self, f: F) -> Mapped<T, V>
238 where
239 F: FnOnce(&U) -> &V,
240 V: ?Sized,
241 {
242 Mapped {
243 mapped: f(self.get_ref()).into(),
244 inner: self.inner,
245 }
246 }
247 /// Attempt to map the reference again, but with the ability to short-circuit on errors.
248 #[inline]
249 pub fn try_and_then<F, V, E>(self, f: F) -> Result<Mapped<T, V>, E>
250 where
251 F: FnOnce(&U) -> Result<&V, E>,
252 V: ?Sized,
253 {
254 Ok(Mapped {
255 mapped: f(self.get_ref())?.into(),
256 inner: self.inner,
257 })
258 }
259}
260impl<T, U> fmt::Debug for Mapped<T, U>
261where
262 T: Guarded,
263 U: ?Sized + fmt::Debug,
264{
265 #[cold]
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 f.debug_tuple("Mapped").field(&self.get_ref()).finish()
268 }
269}
270
271unsafe impl<T, U> Guarded for Mapped<T, U>
272where
273 T: Guarded,
274 U: ?Sized,
275{
276 type Target = U;
277
278 #[inline]
279 fn borrow_guarded(&self) -> &Self::Target {
280 self.get_ref()
281 }
282}
283
284/// A mapped guard, which contains a guarded owned pointer, and a mutable reference to that
285/// pointer. It has had a one-time closure applied to it, but only the _output_ of the closure is
286/// stored, not the closure itself. This is similar to how crates like `owning_ref` work.
287pub struct MappedMut<T, U>
288where
289 T: GuardedMut,
290 U: ?Sized,
291{
292 // NOTE: While we are allowed to take a reference to self, which in turn contains inner, as
293 // well as inner itself, but we are not allowed to create a reference directly to the pointee
294 // inner, since that would violate the no-aliasing rule enforced by Rust's mutable references.
295 inner: T,
296 mapped: NonNull<U>,
297
298 // NOTE: We need to make this invariant, since we have a mutable reference, together with the
299 // data that it points to. If it were to be covariant, then MappedMut<Subtype, Subtype>
300 // would be trivially castable to MappedMut<Subtype, Supertype>. This is a problem since the
301 // reference is mutable, allowing Subtype to be replaced with Supertype.
302 //
303 // The regular Mapped wrapper only deals with immutable references, and is thus covariant.
304 //
305 // See [the nomicon](https://doc.rust-lang.org/nomicon/subtyping.html).
306 _invariance: PhantomData<*mut U>,
307}
308
309impl<T, U> MappedMut<T, U>
310where
311 T: GuardedMut,
312 U: ?Sized,
313{
314 /// Move out the original pointer from the mapped guard, hence cancelling the temporary borrow.
315 #[inline]
316 pub fn into_original(self) -> T {
317 self.inner
318 }
319 /// Convert `MappedMut<T, U>` to `Mapped<T, U>`.
320 #[inline]
321 pub fn into_immutable(this: MappedMut<T, U>) -> Mapped<T, U> {
322 Mapped {
323 inner: this.inner,
324 mapped: this.mapped,
325 }
326 }
327 /// Get an immutable reference to the mapped data.
328 #[inline]
329 pub fn get_ref(&self) -> &U {
330 unsafe { self.mapped.as_ref() }
331 }
332 /// Get a mutable reference to the mapped data.
333 #[inline]
334 pub fn get_mut(&mut self) -> &mut U {
335 unsafe { self.mapped.as_mut() }
336 }
337 /// Map the mapped reference again, converting `&mut U` to `&mut V`.
338 #[inline]
339 pub fn and_then<F, V>(mut self, f: F) -> MappedMut<T, V>
340 where
341 F: FnOnce(&mut U) -> &mut V,
342 V: ?Sized,
343 {
344 MappedMut {
345 mapped: f(self.get_mut()).into(),
346 inner: self.inner,
347
348 _invariance: PhantomData,
349 }
350 }
351
352 /// Attempt to map the reference again, but with the ability to short-circuit on errors.
353 #[inline]
354 pub fn try_and_then<F, V, E>(mut self, f: F) -> Result<MappedMut<T, V>, E>
355 where
356 F: FnOnce(&mut U) -> Result<&mut V, E>,
357 V: ?Sized,
358 {
359 Ok(MappedMut {
360 mapped: f(self.get_mut())?.into(),
361 inner: self.inner,
362
363 _invariance: PhantomData,
364 })
365 }
366}
367
368unsafe impl<T, U> Guarded for MappedMut<T, U>
369where
370 T: GuardedMut,
371 U: ?Sized,
372{
373 type Target = U;
374
375 #[inline]
376 fn borrow_guarded(&self) -> &Self::Target {
377 self.get_ref()
378 }
379}
380unsafe impl<T, U> GuardedMut for MappedMut<T, U>
381where
382 T: GuardedMut,
383 U: ?Sized,
384{
385 #[inline]
386 fn borrow_guarded_mut(&mut self) -> &mut Self::Target {
387 self.get_mut()
388 }
389}
390
391// TODO: Dynamically tracked "anchors" that allow referenced with lifetimes, with the cost of some
392// extra runtime tracking, to prevent dropped or leaked referenced from doing harm. This would be
393// similar to what the old API did.
394
395// TODO: Support mapped types that map into a borrowing _object_, rather than a plain reference.
396// It's not entirely clear what owning_ref does wrong there, but consider [this
397// issue](https://github.com/Kimundi/owning-ref-rs/issues/49).
398
399mod private {
400 pub trait Sealed {}
401}
402/// An extension trait for convenience methods, that is automatically implemented for all
403/// [`Guarded`] types.
404pub trait GuardedExt: private::Sealed + Guarded + Sized {
405 /// Apply a function to the pointee, creating a new guarded type that dereferences into the
406 /// result of that function.
407 ///
408 /// The closure is only evaluated once, and the resulting wrapper will only store one
409 /// null-optimizable additional word, for the reference.
410 #[inline]
411 fn map<F, T>(this: Self, f: F) -> Mapped<Self, T>
412 where
413 F: FnOnce(&<Self as Guarded>::Target) -> &T,
414 T: ?Sized,
415 {
416 Mapped {
417 mapped: f(this.borrow_guarded()).into(),
418 inner: this,
419 }
420 }
421 /// Apply a fallible function to the pointee, creating a new guarded type that dereferences
422 /// into the result of that function.
423 ///
424 /// If the function fails, the error is returned directly, and no further mapping is made.
425 #[inline]
426 fn try_map<F, T, E>(this: Self, f: F) -> Result<Mapped<Self, T>, E>
427 where
428 F: FnOnce(&<Self as Guarded>::Target) -> Result<&T, E>,
429 T: ?Sized,
430 {
431 Ok(Mapped {
432 mapped: f(this.borrow_guarded())?.into(),
433 inner: this,
434 })
435 }
436}
437/// An extension trait for convenience methods, that is automatically implemented for all
438/// [`GuardedMut`] types.
439pub trait GuardedMutExt: private::Sealed + GuardedMut + Sized {
440 /// Apply a function to the pointee, creating a new guarded type that dereferences into the
441 /// result of that function.
442 ///
443 /// This is the mutable version of [`GuardedExt::map`]. Because of this mutability, the
444 /// original pointer cannot be accessed until it is completely moved out of the wrapper.
445 #[inline]
446 fn map_mut<F, T>(mut this: Self, f: F) -> MappedMut<Self, T>
447 where
448 F: FnOnce(&mut <Self as Guarded>::Target) -> &mut T,
449 T: ?Sized,
450 {
451 MappedMut {
452 mapped: f(this.borrow_guarded_mut()).into(),
453 inner: this,
454 _invariance: PhantomData,
455 }
456 }
457 /// Apply a fallible function to the pointee, creating a new guarded type that dereferences
458 /// into the result of that function.
459 ///
460 /// If the function fails, the error is returned directly, and no further mapping is made.
461 #[inline]
462 fn try_map_mut<F, T, E>(mut this: Self, f: F) -> Result<MappedMut<Self, T>, E>
463 where
464 F: FnOnce(&mut <Self as Guarded>::Target) -> Result<&mut T, E>,
465 T: ?Sized,
466 {
467 Ok(MappedMut {
468 mapped: f(this.borrow_guarded_mut())?.into(),
469 inner: this,
470 _invariance: PhantomData,
471 })
472 }
473}
474impl<T> private::Sealed for T
475where
476 T: Guarded + Sized,
477{
478}
479impl<T> GuardedExt for T
480where
481 T: Guarded + Sized,
482{
483}
484impl<T> GuardedMutExt for T
485where
486 T: GuardedMut + Sized,
487{
488}
489
490// TODO: Perhaps an additional extension trait that allows indexing and slicing pointers to slice
491// types?
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496
497 #[test]
498 fn basic_types_implement_guarded() {
499 use std::rc::Rc;
500 use std::sync::Arc;
501
502 fn does_impl_guarded<T: Guarded>() {}
503 fn does_impl_guarded_mut<T: GuardedMut>() {}
504
505 does_impl_guarded::<Arc<[u8]>>();
506 does_impl_guarded::<Rc<[u8]>>();
507 does_impl_guarded::<Box<[u8]>>();
508 does_impl_guarded::<Vec<u8>>();
509 does_impl_guarded::<&'static [u8]>();
510 does_impl_guarded::<&'static mut [u8]>();
511 does_impl_guarded::<&'static str>();
512 does_impl_guarded::<String>();
513
514 does_impl_guarded_mut::<Box<[u8]>>();
515 does_impl_guarded_mut::<Vec<u8>>();
516 does_impl_guarded::<&'static mut [u8]>();
517 }
518
519 #[test]
520 fn mapped() {
521 let mut buf = vec! [0x00_u8; 256];
522
523 let sub_buf = GuardedMutExt::map_mut(buf, |buf: &mut [u8]| -> &mut [u8] {
524 &mut buf[128..]
525 });
526 let mut subsub_buf = sub_buf.and_then(|buf| &mut buf[..64]);
527
528 for byte in subsub_buf.borrow_guarded_mut() {
529 *byte = 0xFF;
530 }
531 buf = subsub_buf.into_original();
532
533 assert!(buf[..128].iter().copied().all(|byte| byte == 0x00));
534 assert!(buf[128..192].iter().copied().all(|byte| byte == 0xFF));
535 assert!(buf[192..].iter().copied().all(|byte| byte == 0x00));
536 }
537 // TODO: try_and_then, etc
538}