Skip to main content

ostd/mm/
vm_space.rs

1// SPDX-License-Identifier: MPL-2.0
2//! Virtual memory space management.
3//!
4//! The [`VmSpace`] struct is provided to manage the virtual memory space of a
5//! user. Cursors are used to traverse and modify over the virtual memory space
6//! concurrently. The VM space cursor [`self::Cursor`] is just a wrapper over
7//! the page table cursor, providing efficient, powerful concurrent accesses
8//! to the page table.
9use alloc::vec::Vec;
10use vstd::atomic::PermissionU64;
11use vstd::pervasive::{arbitrary, proof_from_false};
12use vstd::prelude::*;
13use vstd::simple_pptr::PointsTo;
14use vstd::vpanic;
15
16use crate::error::Error;
17use crate::mm::frame::MetaSlot;
18use crate::mm::frame::meta::mapping::meta_to_frame;
19use crate::mm::frame::untyped::UFrame;
20use crate::mm::kspace::KernelPtConfig;
21use crate::mm::page_table::*;
22use crate::mm::page_table::{EntryOwner, PageTableFrag, PageTableGuard};
23use crate::specs::arch::*;
24use crate::specs::mm::frame::meta_owners::{MetaPerm, MetaSlotStorage, MetadataInnerPerms};
25use crate::specs::mm::frame::meta_region_owners::MetaRegionOwners;
26use crate::specs::mm::io::VmIoMemView;
27use crate::specs::mm::page_table::cursor::owners::CursorOwner;
28use crate::specs::mm::page_table::*;
29use crate::specs::mm::tlb::TlbModel;
30use crate::specs::mm::virt_mem::{MemView, VirtPtr};
31use crate::specs::task::InAtomicMode;
32use crate::sync::RoArc;
33use core::marker::PhantomData;
34use core::{ops::Range, sync::atomic::Ordering};
35use vstd_extra::ghost_tree::*;
36use vstd_extra::panic::may_panic;
37use vstd_extra::{assert, assert_eq};
38
39use crate::mm::kspace::KERNEL_PAGE_TABLE;
40use crate::mm::tlb::*;
41use crate::specs::mm::cpu::{AtomicCpuSet, CpuSet};
42
43use crate::specs::mm::io::VmIoOwner;
44use crate::{
45    mm::{
46        MAX_USERSPACE_VADDR, Paddr, PagingConstsTrait, PagingLevel, Vaddr,
47        io::{Fallible, VmReader, VmWriter},
48        page_prop::PageProperty,
49    },
50    prelude::*,
51};
52
53use alloc::sync::Arc;
54
55#[path = "../../specs/mm/vm_space.rs"]
56pub mod vm_space_specs;
57use vm_space_specs::*;
58
59verus! {
60
61/// A virtual address space for user-mode tasks, enabling safe manipulation of user-space memory.
62///
63/// The [`VmSpace`] type provides memory isolation guarantees between user-space and
64/// kernel-space. For example, given an arbitrary user-space pointer, one can read and
65/// write the memory location referred to by the user-space pointer without the risk of
66/// breaking the memory safety of the kernel space.
67///
68/// # Task Association Semantics
69///
70/// As far as OSTD is concerned, a [`VmSpace`] is not necessarily associated with a task. Once a
71/// [`VmSpace`] is activated (see [`VmSpace::activate`]), it remains activated until another
72/// [`VmSpace`] is activated **possibly by another task running on the same CPU**.
73///
74/// This means that it's up to the kernel to ensure that a task's [`VmSpace`] is always activated
75/// while the task is running. This can be done by using the injected post schedule handler
76/// (see [`inject_post_schedule_handler`]) to always activate the correct [`VmSpace`] after each
77/// context switch.
78///
79/// If the kernel otherwise decides not to ensure that the running task's [`VmSpace`] is always
80/// activated, the kernel must deal with race conditions when calling methods that require the
81/// [`VmSpace`] to be activated, e.g., [`UserMode::execute`], [`VmReader`] and [`VmWriter`].
82/// Otherwise, the behavior is unspecified, though it's guaranteed _not_ to compromise the kernel's
83/// memory safety.
84///
85/// # Memory Backing
86///
87/// A newly-created [`VmSpace`] is not backed by any physical memory pages. To
88/// provide memory pages for a [`VmSpace`], one can allocate and map physical
89/// memory ([`UFrame`]s) to the [`VmSpace`] using the cursor.
90///
91/// A [`VmSpace`] can also attach a page fault handler, which will be invoked to
92/// handle page faults generated from user space.
93///
94/// [`inject_post_schedule_handler`]: crate::task::inject_post_schedule_handler
95/// [`UserMode::execute`]: crate::user::UserMode::execute
96/// [`VmReader`]: crate::mm::io::VmWriter
97/// [`VmReader`]: crate::mm::io::VmReader
98/// # Verification Design
99///
100/// A [`VmSpace`] has a corresponding [`VmSpaceOwner`] object that is used to track its state,
101/// and against which its invariants are stated. The [`VmSpaceOwner`] catalogues the readers and writers
102/// that are associated with the [`VmSpace`], and the [`MemView`] which encodes the active page table and
103/// the subset of the TLB that covers the same virtual address space.
104/// All proofs about the correctness of the readers and writers are founded on the well-formedness of the [`MemView`]:
105///
106/// ```rust
107/// open spec fn mem_view_wf(self) -> bool {
108///    &&& self.mem_view is Some <==> self.mv_range@ is Some
109///    // This requires that TotalMapping (mvv) = mv ∪ writer mappings ∪ reader mappings
110///    &&& self.mem_view matches Some(remaining_view)
111///        ==> self.mv_range@ matches Some(total_view)
112///        ==> {
113///        &&& remaining_view.mappings_are_disjoint()
114///        &&& remaining_view.mappings.finite()
115///        &&& total_view.mappings_are_disjoint()
116///        &&& total_view.mappings.finite()
117///        // ======================
118///        // Remaining Consistency
119///        // ======================
120///        &&& remaining_view.mappings.subset_of(total_view.mappings)
121///        &&& remaining_view.memory.dom().subset_of(
122///            total_view.memory.dom(),
123///        )
124///        // =====================
125///        // Total View Consistency
126///        // =====================
127///        &&& forall|va: usize|
128///            remaining_view.addr_transl(va) == total_view.addr_transl(
129///                va,
130///            )
131///        // =====================
132///        // Writer correctness
133///        // =====================
134///        &&& forall|i: int|
135///            0 <= i < self.writers.len() as int ==> {
136///                &&& self.writers[i].inv()
137///            }
138///        }
139///    }
140/// }
141/// ```
142pub struct VmSpace<'a> {
143    pub pt: PageTable<UserPtConfig>,
144    pub cpus: AtomicCpuSet,
145    pub _marker: PhantomData<&'a ()>,
146}
147
148type Result<A> = core::result::Result<A, Error>;
149
150#[verus_verify]
151impl<'a> VmSpace<'a> {
152    #[inline]
153    #[verus_spec(r =>
154        with
155            Tracked(regions): Tracked<&mut MetaRegionOwners>,
156            Tracked(guards): Tracked<&mut Guards<'rcu>>,
157        requires
158            old(regions).inv(),
159    )]
160    #[allow(private_interfaces)]
161    pub fn default<'rcu>() -> Self {
162        proof_with!(Tracked(regions), Tracked(guards));
163        Self::new()
164    }
165
166    /// Creates a new VM address space.
167    ///
168    /// This allocates a new user page table by duplicating the kernel page
169    /// table's top-level entries, and returns a [`VmSpace`] that wraps it.
170    ///
171    /// # Verified Properties
172    /// ## Preconditions
173    /// - **Safety Invariants**: The meta-region invariants must hold.
174    /// ## Postconditions
175    /// - The returned [`VmSpace`] instance satisfies the invariants of [`VmSpace`].
176    #[inline]
177    #[verus_spec(r =>
178        with
179            Tracked(regions): Tracked<&mut MetaRegionOwners>,
180            Tracked(guards): Tracked<&mut Guards<'rcu>>,
181        requires
182            old(regions).inv(),
183        ensures
184            final(regions).inv(),
185    )]
186    #[allow(private_interfaces)]
187    pub fn new<'rcu>() -> Self {
188        proof_decl! {
189            let tracked mut kernel_owner_opt: Option<&PageTableOwner<KernelPtConfig>> = None;
190        }
191        let kpt = crate::mm::kspace::kvirt_area::get_kernel_page_table(
192            Tracked(&mut kernel_owner_opt),
193            Tracked(regions),
194            Tracked(guards),
195        );
196        proof_decl! {
197            let tracked kernel_owner = kernel_owner_opt.tracked_take();
198        }
199        let pt = {
200            #[verus_spec(with Tracked(kernel_owner), Tracked(regions), Tracked(guards))]
201            kpt.create_user_page_table::<crate::specs::task::AnyAtomicGuard>()
202        };
203        Self { pt, cpus: AtomicCpuSet::new(CpuSet::new_empty()), _marker: PhantomData }
204    }
205
206    /// Gets an immutable cursor in the virtual address range.
207    ///
208    /// The cursor behaves like a lock guard, exclusively owning a sub-tree of
209    /// the page table, preventing others from creating a cursor in it. So be
210    /// sure to drop the cursor as soon as possible.
211    ///
212    /// The creation of the cursor may block if another cursor having an
213    /// overlapping range is alive.
214    ///
215    /// # Verified Properties
216    /// ## Preconditions
217    /// - **Safety Invariants**: The page table owner must be valid.
218    /// ## Postconditions
219    /// - When the virtual address range satisfies
220    ///   [`cursor_new_success_conditions`](crate::mm::page_table::Cursor::cursor_new_success_conditions),
221    ///   the result is `Ok` and a [`CursorOwner`] is returned.
222    #[verus_spec(r =>
223        with
224            Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
225            Tracked(regions): Tracked<&mut MetaRegionOwners>,
226            Tracked(guards): Tracked<&mut Guards<'a>>,
227                -> cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
228        requires
229            owner.inv(),
230        ensures
231            crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(va) ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),
232            // On the success branch, the returned cursor owner satisfies
233            // its invariant. Follows from the underlying PT::cursor's
234            // ensures: r is Ok ⇒ cursor_new_success_conditions (by
235            // contrapositive of the !cond ⇒ Err clause) ⇒ invariants
236            // hold ⇒ cursor_owner.inv().
237            cursor_owner@ matches Some(c) ==> c.inv(),
238    )]
239    pub fn cursor<G: InAtomicMode>(&'a self, guard: &'a G, va: &Range<Vaddr>) -> Result<
240        Cursor<'a, G>,
241    > {
242        proof_decl! {
243            let tracked mut out_owner: Option<CursorOwner<'a, UserPtConfig>>;
244        }
245        match {
246            #[verus_spec(with Tracked(owner), Ghost(vstd::pervasive::arbitrary::<PageTableGuard<'a, UserPtConfig>>()), Tracked(regions), Tracked(guards))]
247            self.pt.cursor(guard, va)
248        } {
249            Ok((pt_cursor, tracked_owner)) => {
250                proof! { out_owner = Some::<CursorOwner<'a, UserPtConfig>>(tracked_owner.get()); }
251                proof_with!(|= Tracked(out_owner));
252                Ok(Cursor(pt_cursor))
253            },
254            Err(e) => {
255                proof! { out_owner = None; }
256                proof_with!(|= Tracked(out_owner));
257                Err(Error::AccessDenied)
258            },
259        }
260    }
261
262    /// Gets a mutable cursor in the virtual address range.
263    ///
264    /// The same as [`Self::cursor`], the cursor behaves like a lock guard,
265    /// exclusively owning a sub-tree of the page table, preventing others
266    /// from creating a cursor in it. So be sure to drop the cursor as soon as
267    /// possible.
268    ///
269    /// The creation of the cursor may block if another cursor having an
270    /// overlapping range is alive. The modification to the mapping by the
271    /// cursor may also block or be overridden by the mapping of another cursor.
272    ///
273    /// # Verified Properties
274    /// ## Preconditions
275    /// - **Safety Invariants**: The page table owner must be valid.
276    /// ## Postconditions
277    /// - When the virtual address range satisfies
278    ///   [`cursor_new_success_conditions`](crate::mm::page_table::Cursor::cursor_new_success_conditions),
279    ///   the result is `Ok` and a [`CursorOwner`] is returned.
280    #[verus_spec(r =>
281        with
282            Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
283            Tracked(regions): Tracked<&mut MetaRegionOwners>,
284            Tracked(guards): Tracked<&mut Guards<'a>>
285                -> cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
286        requires
287            owner.inv(),
288        ensures
289            crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(va) ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),
290            // See `cursor` above for the derivation.
291            cursor_owner@ matches Some(c) ==> c.inv(),
292    )]
293    pub fn cursor_mut<G: InAtomicMode>(&'a self, guard: &'a G, va: &Range<Vaddr>) -> Result<
294        CursorMut<'a, G>,
295    > {
296        proof_decl! {
297            let tracked mut out_owner: Option<CursorOwner<'a, UserPtConfig>>;
298        }
299        match {
300            #[verus_spec(with Tracked(owner), Ghost(vstd::pervasive::arbitrary::<PageTableGuard<'a, UserPtConfig>>()), Tracked(regions), Tracked(guards))]
301            self.pt.cursor_mut(guard, va)
302        } {
303            Ok((pt_cursor, tracked_owner)) => {
304                proof! { out_owner = Some::<CursorOwner<'a, UserPtConfig>>(tracked_owner.get()); }
305                proof_with!(|= Tracked(out_owner));
306                Ok(CursorMut { pt_cursor, flusher: TlbFlusher::new(&self.cpus) })
307            },
308            Err(e) => {
309                proof! { out_owner = None; }
310                proof_with!(|= Tracked(out_owner));
311                Err(Error::AccessDenied)
312            },
313        }
314    }
315
316    /// Activates the page table on the current CPU.
317    #[verifier::external_body]
318    pub fn activate(this: &RoArc<Self>) {
319        // No support for CPU set semantics; skip now
320        // let preempt_guard = disable_preempt();
321        // let cpu = preempt_guard.current_cpu();
322        // let last_ptr = ACTIVATED_VM_SPACE.load();
323        // if last_ptr == Arc::as_ptr(self) {
324        //     return;
325        // }
326        // // Record ourselves in the CPU set and the activated VM space pointer.
327        // // `Acquire` to ensure the modification to the PT is visible by this CPU.
328        // self.cpus.add(cpu, Ordering::Acquire);
329        // let self_ptr = Arc::into_raw(Arc::clone(self)) as *mut VmSpace;
330        // ACTIVATED_VM_SPACE.store(self_ptr);
331        // if !last_ptr.is_null() {
332        //     // SAFETY: The pointer is cast from an `Arc` when it's activated
333        //     // the last time, so it can be restored and only restored once.
334        //     let last = unsafe { Arc::from_raw(last_ptr) };
335        //     last.cpus.remove(cpu, Ordering::Relaxed);
336        // }
337        // self.pt.activate();
338        unimplemented!()
339    }
340
341    /// Creates a reader to read data from the user space of the current task.
342    ///
343    /// Returns [`Err`] if this [`VmSpace`] is not the user space of the current task
344    /// or the `vaddr` and `len` do not represent a valid user space memory range.
345    ///
346    /// # Verified Properties
347    /// ## Preconditions
348    /// - The [`VmSpaceOwner`] invariant must hold.
349    /// ## Postconditions
350    /// - When [`Self::reader_success_cond`] holds, the result is `Ok`.
351    /// - On success, the [`VmReader`] and its [`VmIoOwner`] are well-formed with no memory view.
352    /// ## Safety
353    /// - The function does not interact with the lower-level memory system directly.
354    ///   By checking that the target (user) page table is not the active (kernel) one,
355    ///   we ensure that the resulting reader cannot interact with kernel memory.
356    #[inline]
357    #[verus_spec(r =>
358        with
359            Tracked(owner): Tracked<&'a mut VmSpaceOwner>,
360                -> reader_owner: Tracked<Option<VmIoOwner>>,
361        requires
362            old(owner).inv(),
363        ensures
364            final(owner).inv(),
365            self.reader_success_cond(vaddr, len) ==> r is Ok && reader_owner@ is Some,
366            r is Ok && reader_owner@ is Some ==> {
367                &&& r.unwrap().wf(reader_owner@.unwrap())
368                &&& reader_owner@.unwrap().mem_view is None
369                &&& reader_owner@.unwrap().inv()
370            },
371            // Range bound is necessary for success: the body's `checked_add`
372            // guard rejects any out-of-range request before constructing a
373            // reader. `nat` arithmetic subsumes the overflow case.
374            r is Ok ==> (vaddr as nat) + (len as nat) <= MAX_USERSPACE_VADDR as nat,
375    )]
376    pub fn reader(&self, vaddr: Vaddr, len: usize) -> Result<VmReader<'a, Fallible>> {
377        if current_page_table_paddr() != self.pt.root_paddr() {
378            proof_with!(|= Tracked(None));
379            Err(Error::AccessDenied)
380        } else if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR {
381            proof_with!(|= Tracked(None));
382            Err(Error::AccessDenied)
383        } else {
384            let ghost id = owner.new_vm_io_id();
385            proof_decl! {
386                let tracked mut vm_reader_owner: VmIoOwner;
387            }
388
389            // SAFETY: The memory range is in user space, as checked above.
390            let reader = unsafe {
391                proof_with!(Ghost(id) => Tracked(vm_reader_owner));
392                VmReader::from_user_space(VirtPtr::from_vaddr(vaddr, len), len)
393            };
394
395            proof_with!(|= Tracked(Some(vm_reader_owner)));
396            Ok(reader)
397        }
398    }
399
400    /// Creates a writer to write data to the user space of the current task.
401    ///
402    /// Returns [`Err`] if this [`VmSpace`] is not the user space of the current task
403    /// or the `vaddr` and `len` do not represent a valid user space memory range.
404    ///
405    /// # Verified Properties
406    /// ## Preconditions
407    /// - The [`VmSpaceOwner`] invariant must hold.
408    /// ## Postconditions
409    /// - When [`Self::writer_success_cond`] holds, the result is `Ok`.
410    /// - On success, the [`VmWriter`] and its [`VmIoOwner`] are well-formed with no memory view.
411    /// ## Safety
412    /// - The function does not interact with the lower-level memory system directly.
413    ///   By checking that the target (user) page table is not the active (kernel) one,
414    ///   we ensure that the resulting writer cannot interact with kernel memory.
415    #[inline]
416    #[verus_spec(r =>
417        with
418            Tracked(owner): Tracked<&mut VmSpaceOwner>,
419                -> writer_owner: Tracked<Option<VmIoOwner>>,
420        requires
421            old(owner).inv(),
422        ensures
423            final(owner).inv(),
424            self.writer_success_cond(vaddr, len) ==> r is Ok && writer_owner@ is Some,
425            r is Ok && writer_owner@ is Some ==> {
426                &&& r.unwrap().wf(writer_owner@.unwrap())
427                &&& writer_owner@.unwrap().mem_view is None
428                &&& writer_owner@.unwrap().inv()
429            },
430            // Range bound is necessary for success: see `reader` above.
431            r is Ok ==> (vaddr as nat) + (len as nat) <= MAX_USERSPACE_VADDR as nat,
432    )]
433    pub fn writer(self, vaddr: Vaddr, len: usize) -> Result<VmWriter<'a, Fallible>> {
434        if current_page_table_paddr() != self.pt.root_paddr() {
435            proof_with!(|= Tracked(None));
436            Err(Error::AccessDenied)
437        } else if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR {
438            proof_with!(|= Tracked(None));
439            Err(Error::AccessDenied)
440        } else {
441            let ghost id = owner.new_vm_io_id();
442            proof_decl! {
443                let tracked mut vm_writer_owner: VmIoOwner;
444            }
445
446            // SAFETY: The memory range is in user space, as checked above.
447            let reader = unsafe {
448                proof_with!(Ghost(id) => Tracked(vm_writer_owner));
449                VmWriter::from_user_space(VirtPtr::from_vaddr(vaddr, len), len)
450            };
451
452            proof_with!(|= Tracked(Some(vm_writer_owner)));
453            Ok(reader)
454        }
455    }
456}
457
458/// The cursor for querying over the VM space without modifying it.
459///
460/// It exclusively owns a sub-tree of the page table, preventing others from
461/// reading or modifying the same sub-tree. Two read-only cursors can not be
462/// created from the same virtual address range either.
463pub struct Cursor<'a, A: InAtomicMode>(pub crate::mm::page_table::Cursor<'a, UserPtConfig, A>);
464
465#[verus_verify]
466impl<'rcu, A: InAtomicMode> Cursor<'rcu, A> {
467    /// Queries the mapping at the current virtual address.
468    ///
469    /// If the cursor is pointing to a valid virtual address that is locked,
470    /// it will return the virtual address range and the mapped item.
471    /// ## Preconditions
472    /// - All system invariants must hold
473    /// - **Liveness**: The function will return an error if the cursor is not within the locked range
474    /// ## Postconditions
475    /// - If there is a mapped item at the current virtual address ([`query_some_condition`]),
476    /// it is returned along with the virtual address range that it maps ([`query_success_ensures`]).
477    /// - The mapping that is returned corresponds to the abstract mapping given by [`query_item_spec`](CursorView::query_item_spec).
478    /// - If there is no mapped item at the current virtual address ([`query_none_condition`]),
479    /// it returns [`None`], and the virtual address range of the cursor's current position.
480    /// ## Safety
481    /// - This function preserves all memory invariants.
482    /// - The locking mechanism prevents data races.
483    #[verus_spec(r =>
484        with
485            Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
486            Tracked(regions): Tracked<&mut MetaRegionOwners>,
487            Tracked(guards): Tracked<&mut Guards<'rcu>>,
488        requires
489            old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
490            // Out-of-range is a graceful `Err`; the sole panic is cloning
491            // the resolved leaf frame when *that specific slot* is
492            // saturated — precisely propagated from
493            // `Cursor::query_panic_condition`.
494            old(self).0.query_panic_condition(*old(owner), *old(regions)) ==> may_panic(),
495        ensures
496            final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
497            !old(self).0.query_panic_condition(*old(owner), *old(regions)),
498            old(owner).in_locked_range() ==> r is Ok,
499            r matches Ok(state) ==>
500                final(self).0.query_some_condition(*final(owner)) ==>
501                final(self).0.query_some_ensures(*final(owner), state),
502            r matches Ok(state) ==>
503                !final(self).0.query_some_condition(*final(owner)) ==>
504                final(self).0.query_none_ensures(*final(owner), state),
505            old(owner)@.mappings == final(owner)@.mappings,
506    )]
507    pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<MappedItem>)> {
508        Ok(
509            #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
510            self.0.query()?,
511        )
512    }
513
514    /// Moves the cursor forward to the next mapped virtual address.
515    ///
516    /// If there is mapped virtual address following the current address within
517    /// next `len` bytes, it will return that mapped address. In this case,
518    /// the cursor will stop at the mapped address.
519    ///
520    /// Otherwise, it will return `None`. And the cursor may stop at any
521    /// address after `len` bytes.
522    ///
523    /// # Verified Properties
524    /// ## Preconditions
525    /// - **Safety Invariants**: The page table cursor safety invariants
526    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
527    /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
528    /// ## Postconditions
529    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
530    /// - **Correctness**: If there is a mapped address after the current address within the next `len` bytes,
531    /// it will move the cursor to the next mapped address and return it.
532    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
533    /// ## Panics
534    /// This method panics if the length is longer than the remaining range of the cursor.
535    /// ## Safety
536    /// This function preserves all memory invariants.
537    /// Because it panics rather than move the cursor to an invalid address,
538    /// it ensures that the cursor is safe to use after the call.
539    #[verus_spec(res =>
540        with
541            Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
542            Tracked(regions): Tracked<&mut MetaRegionOwners>,
543            Tracked(guards): Tracked<&mut Guards<'rcu>>,
544        requires
545            old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
546            old(self).0.find_next_panic_condition(len) ==> may_panic(),
547        ensures
548            !old(self).0.find_next_panic_condition(len),
549            final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
550            res is Some ==> {
551                &&& res.unwrap() == final(self).0.va
552                &&& final(owner).level <= final(owner).guard_level
553                &&& final(owner).in_locked_range()
554            },
555    )]
556    pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
557        #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
558        self.0.find_next(len)
559    }
560
561    /// Jump to the virtual address.
562    ///
563    /// This function will move the cursor to the given virtual address.
564    /// If the target address is not in the locked range, it will return an error.
565    /// # Verified Properties
566    /// ## Preconditions
567    /// - **Safety Invariants**: The page table cursor safety invariants
568    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
569    /// - **Liveness**:  The function will panic if the target `va` is not aligned
570    /// to the base page size.
571    /// ## Postconditions
572    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
573    /// - **Correctness**: If the target `va` is within the cursor's locked range,
574    /// the result will be `Ok` and the cursor's virtual address will be set to `va`.
575    /// - **Correctness**: If the target `va` is outside the locked range, the result is `Err`.
576    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
577    /// ## Panics
578    /// This method panics if the target address is not aligned to the page size.
579    /// ## Safety
580    /// This function preserves all memory invariants.
581    /// Because it throws an error rather than move the cursor to an invalid address,
582    /// it ensures that the cursor is safe to use after the call.
583    /// The locking mechanism prevents data races.
584    #[verus_spec(res =>
585        with
586            Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
587            Tracked(regions): Tracked<&mut MetaRegionOwners>,
588            Tracked(guards): Tracked<&mut Guards<'rcu>>,
589        requires
590            old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
591            // `CursorMut::jump` diverges on a misaligned `va` and may panic
592            // in its `pop_level` repositioning ascent.
593            old(self).0.jump_panic_condition(va) ==> may_panic(),
594        ensures
595            !old(self).0.jump_panic_condition(va),
596            final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
597            final(self).0.barrier_va.start <= va < final(self).0.barrier_va.end ==> {
598                &&& res is Ok
599                &&& final(self).0.va == va
600            },
601            !(final(self).0.barrier_va.start <= va < final(self).0.barrier_va.end) ==> res is Err,
602    )]
603    pub fn jump(&mut self, va: Vaddr) -> Result<()> {
604        (#[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
605        self.0.jump(va))?;
606        Ok(())
607    }
608
609    /// Get the virtual address of the current slot.
610    #[verus_spec(
611        returns
612            self.0.va,
613    )]
614    pub fn virt_addr(&self) -> Vaddr {
615        self.0.virt_addr()
616    }
617}
618
619/// The cursor for modifying the mappings in VM space.
620///
621/// It exclusively owns a sub-tree of the page table, preventing others from
622/// reading or modifying the same sub-tree.
623pub struct CursorMut<'a, A: InAtomicMode> {
624    pub pt_cursor: crate::mm::page_table::CursorMut<'a, UserPtConfig, A>,
625    // We have a read lock so the CPU set in the flusher is always a superset
626    // of actual activated CPUs.
627    pub flusher: TlbFlusher<'a  /*, DisabledPreemptGuard*/ >,
628}
629
630#[verus_verify]
631impl<'a, A: InAtomicMode> CursorMut<'a, A> {
632    /// Queries the mapping at the current virtual address.
633    ///
634    /// This is the same as [`Cursor::query`].
635    ///
636    /// If the cursor is pointing to a valid virtual address that is locked,
637    /// it will return the virtual address range and the mapped item.
638    /// ## Preconditions
639    /// - **Safety Invariants**: The page table cursor safety invariants
640    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
641    /// ## Postconditions
642    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
643    /// - **Correctness**: If the cursor is within the locked range, the result is `Ok`.
644    /// - **Correctness**: If there is a mapped item at the current virtual address ([`query_some_condition`]),
645    /// it is returned along with the virtual address range that it maps ([`query_success_ensures`]).
646    /// - **Correctness**: The mapping that is returned corresponds to the abstract mapping given by [`query_item_spec`](CursorView::query_item_spec).
647    /// - **Correctness**: If there is no mapped item at the current virtual address ([`query_none_condition`]),
648    /// it returns `None`, and the virtual address range of the cursor's current position.
649    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
650    /// - **Safety**: The mappings in the page table are not affected.
651    /// - **Safety**: The soundness of individual entries are not affected.
652    /// ## Safety
653    /// - This function preserves all memory invariants.
654    /// - The locking mechanism prevents data races.
655    #[verus_spec(res =>
656        with
657            Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
658            Tracked(regions): Tracked<&mut MetaRegionOwners>,
659            Tracked(guards): Tracked<&mut Guards<'a>>,
660        requires
661            old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
662            // Out-of-range → graceful `Err`; the sole panic is cloning the
663            // resolved leaf frame when *that specific slot* is saturated —
664            // precisely propagated from `Cursor::query_panic_condition`.
665            old(self).pt_cursor.0.query_panic_condition(*old(owner), *old(regions))
666                ==> may_panic(),
667        ensures
668            final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
669            !old(self).pt_cursor.0.query_panic_condition(*old(owner), *old(regions)),
670            old(owner).in_locked_range() ==> res is Ok,
671            res matches Ok(state) ==>
672                final(self).pt_cursor.0.query_some_condition(*final(owner)) ==>
673                final(self).pt_cursor.0.query_some_ensures(*final(owner), state),
674            res matches Ok(state) ==>
675                !final(self).pt_cursor.0.query_some_condition(*final(owner)) ==>
676                final(self).pt_cursor.0.query_none_ensures(*final(owner), state),
677            old(owner)@.mappings == final(owner)@.mappings,
678    )]
679    pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<MappedItem>)> {
680        Ok(
681            #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
682            self.pt_cursor.query()?,
683        )
684    }
685
686    /// Moves the cursor forward to the next mapped virtual address.
687    ///
688    /// This is the same as [`Cursor::find_next`].
689    ///
690    /// # Verified Properties
691    /// ## Preconditions
692    /// - **Safety Invariants**: The page table cursor safety invariants
693    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
694    /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
695    /// ## Postconditions
696    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
697    /// - **Correctness**: If there is a mapped address after the current address within the next `len` bytes,
698    /// it will move the cursor to the next mapped address and return it.
699    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
700    /// ## Panics
701    /// This method panics if the length is longer than the remaining range of the cursor.
702    /// ## Safety
703    /// This function preserves all memory invariants.
704    /// Because it panics rather than move the cursor to an invalid address,
705    /// it ensures that the cursor is safe to use after the call.
706    #[verus_spec(res =>
707        with
708            Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
709            Tracked(regions): Tracked<&mut MetaRegionOwners>,
710            Tracked(guards): Tracked<&mut Guards<'a>>,
711        requires
712            old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
713            old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
714        ensures
715            !old(self).pt_cursor.0.find_next_panic_condition(len),
716            final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
717            res is Some ==> {
718                &&& res.unwrap() == final(self).pt_cursor.0.va
719                &&& final(owner).level <= final(owner).guard_level
720                &&& final(owner).in_locked_range()
721            },
722    )]
723    pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
724        #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
725        self.pt_cursor.find_next(len)
726    }
727
728    /// Jump to the virtual address.
729    ///
730    /// This is the same as [`Cursor::jump`].
731    ///
732    /// # Verified Properties
733    /// ## Preconditions
734    /// - **Safety Invariants**: The page table cursor safety invariants
735    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
736    /// - **Liveness**:  The function will panic if the target `va` is not aligned
737    /// to the base page size.
738    /// ## Postconditions
739    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
740    /// - **Correctness**: If the target `va` is within the cursor's locked range,
741    /// the result will be `Ok` and the cursor's virtual address will be set to `va`.
742    /// - **Correctness**: If the target `va` is outside the locked range, the result is `Err`.
743    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
744    /// ## Panics
745    /// This method panics if the target address is not aligned to the page size.
746    /// ## Safety
747    /// This function preserves all memory invariants.
748    /// Because it throws an error rather than move the cursor to an invalid address,
749    /// it ensures that the cursor is safe to use after the call.
750    /// The locking mechanism prevents data races.
751    #[verus_spec(res =>
752        with
753            Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
754            Tracked(regions): Tracked<&mut MetaRegionOwners>,
755            Tracked(guards): Tracked<&mut Guards<'a>>
756        requires
757            old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
758            // `CursorMut::jump` diverges on a misaligned `va` and may panic
759            // in its `pop_level` repositioning ascent.
760            old(self).pt_cursor.0.jump_panic_condition(va) ==> may_panic(),
761        ensures
762            !old(self).pt_cursor.0.jump_panic_condition(va),
763            final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
764            final(self).pt_cursor.0.barrier_va.start <= va < final(self).pt_cursor.0.barrier_va.end ==> {
765                &&& res is Ok
766                &&& final(self).pt_cursor.0.va == va
767            },
768            !(final(self).pt_cursor.0.barrier_va.start <= va < final(self).pt_cursor.0.barrier_va.end) ==> res is Err,
769    )]
770    pub fn jump(&mut self, va: Vaddr) -> Result<()> {
771        (#[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
772        self.pt_cursor.jump(va))?;
773        Ok(())
774    }
775
776    /// Get the virtual address of the current slot.
777    #[verus_spec(r =>
778        returns
779            self.pt_cursor.0.va,
780    )]
781    pub fn virt_addr(&self) -> Vaddr {
782        self.pt_cursor.virt_addr()
783    }
784
785    /// Get the dedicated TLB flusher for this cursor.
786    #[verus_spec(ret =>
787        ensures
788            *ret == old(self).flusher,
789            *final(ret) == final(self).flusher,
790    )]
791    pub fn flusher(&mut self) -> &mut TlbFlusher<'a> {
792        &mut self.flusher
793    }
794
795    /// Map a frame into the current slot.
796    ///
797    /// This method will bring the cursor to the next slot after the modification.
798    /// If there is an existing mapping at the current slot, it will be replaced
799    /// and the TLB will be flushed for that entry.
800    /// # Verified Properties
801    /// ## Preconditions
802    /// - **Safety Invariants**: The page table cursor safety invariants
803    ///   ([`invariants`](crate::mm::page_table::Cursor::invariants)) and the TLB invariant
804    ///   ([`TlbModel::inv`]) must hold before the call.
805    /// - **Liveness**: The cursor must not be past the end of its locked range,
806    ///   and the frame's level must fit within the remaining range, or the function will panic.
807    /// - **Bookkeeping**: The frame must be well-formed with respect to its entry owner
808    ///   ([`item_wf`](Self::item_wf)).
809    /// ## Postconditions
810    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
811    /// - **Correctness**: The page table view is updated with the new mapping
812    ///   according to [`map_item_ensures`](Self::map_item_ensures).
813    /// - **Correctness**: If the metadata region was well-formed before the call
814    ///   and the frame was not already mapped, it will be well-formed after.
815    /// ## Safety
816    /// - For soundness purposes, it doesn't matter if a frame is mapped multiple times
817    /// in the same page table. There is still a clear definition of the behavior.
818    #[verus_spec(
819        with
820            Tracked(cursor_owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
821            Tracked(entry_owner): Tracked<EntryOwner<UserPtConfig>>,
822            Tracked(regions): Tracked<&mut MetaRegionOwners>,
823            Tracked(guards): Tracked<&mut Guards<'a>>,
824            Tracked(tlb_model): Tracked<&mut TlbModel>,
825        requires
826            old(tlb_model).inv(),
827            old(self).pt_cursor.0.invariants(*old(cursor_owner), *old(regions), *old(guards)),
828            old(self).item_wf(frame, prop, entry_owner, *old(regions)),
829            old(self).pt_cursor.map_panic_conditions(MappedItem { frame: frame, prop: prop }) ==> may_panic(),
830        ensures
831            !old(self).pt_cursor.map_panic_conditions(MappedItem { frame: frame, prop: prop }),
832            final(self).pt_cursor.0.invariants(*final(cursor_owner), *final(regions), *final(guards)),
833            old(self).map_item_ensures(
834                frame,
835                prop,
836                old(self).pt_cursor.0.model(*old(cursor_owner)),
837                final(self).pt_cursor.0.model(*final(cursor_owner)),
838            ),
839    )]
840    pub fn map(&mut self, frame: UFrame, prop: PageProperty) {
841        let start_va = self.virt_addr();
842        let item = MappedItem { frame: frame, prop: prop };
843
844        assert(self.pt_cursor.item_wf(item, entry_owner)) by {};
845
846        // SAFETY: It is safe to map untyped memory into the userspace.
847        let Err(frag) = (unsafe {
848            #[verus_spec(with Tracked(cursor_owner), Tracked(entry_owner), Tracked(regions), Tracked(guards))]
849            self.pt_cursor.map(item)
850        }) else {
851            return;  // No mapping exists at the current address.
852        };
853
854        match frag {
855            PageTableFrag::Mapped { va, item } => {
856                //debug_assert_eq!(va, start_va);
857                let old_frame = item.frame;
858
859                #[verus_spec(with Tracked(tlb_model))]
860                self.flusher.issue_tlb_flush_with(
861                    TlbFlushOp::Address(start_va),
862                    old_frame.into_dyn(),
863                );
864                #[verus_spec(with Tracked(tlb_model))]
865                self.flusher.dispatch_tlb_flush();
866            },
867            PageTableFrag::StrayPageTable { .. } => {
868                assert(false) by {
869                    assert(UserPtConfig::item_into_raw(item).1 == 1);
870                };
871                #[cfg(feature = "allow_panic")]
872                vpanic!("`UFrame` is base page sized but re-mapping out a child PT");
873            },
874        }
875    }
876
877    /// Clears the mapping starting from the current slot,
878    /// and returns the number of unmapped pages.
879    ///
880    /// This method will bring the cursor forward by `len` bytes in the virtual
881    /// address space after the modification.
882    ///
883    /// Already-absent mappings encountered by the cursor will be skipped. It
884    /// is valid to unmap a range that is not mapped.
885    ///
886    /// It must issue and dispatch a TLB flush after the operation. Otherwise,
887    /// the memory safety will be compromised. Please call this function less
888    /// to avoid the overhead of TLB flush. Using a large `len` is wiser than
889    /// splitting the operation into multiple small ones.
890    ///
891    /// # Verified Properties
892    /// ## Preconditions
893    /// - **Safety Invariants**: The page table cursor safety invariants
894    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
895    /// - **Safety Invariants**: The TLB invariant ([TlbModel::inv]) must hold.
896    /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
897    /// ## Postconditions
898    /// - **Safety Invariants**: The page table cursor safety invariants are preserved.
899    /// - **Safety Invariants**: The TLB invariant is preserved.
900    /// - **Correctness**: Unmaps a range of virtual addresses from the current address up to `len` bytes
901    /// and returns the number of mappings that were removed.
902    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
903    /// ## Panics
904    /// Panics if:
905    ///  - the length is longer than the remaining range of the cursor;
906    ///  - the length is not page-aligned.
907    /// ## Safety
908    /// - It is always sound to unmap pages. We flush unmapped pages from the TLB to ensure consistency.
909    /// - TODO: formalizing and proving that this function preserves TLB consistency would
910    /// be pretty straightforward and would be a nice addition to the correctness properties.
911    #[verus_spec(r =>
912        with
913            Tracked(cursor_owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
914            Tracked(regions): Tracked<&mut MetaRegionOwners>,
915            Tracked(guards): Tracked<&mut Guards<'a>>,
916            Tracked(tlb_model): Tracked<&mut TlbModel>,
917        requires
918            old(self).pt_cursor.0.invariants(*old(cursor_owner), *old(regions), *old(guards)),
919            old(tlb_model).inv(),
920            old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
921        ensures
922            !old(self).pt_cursor.0.find_next_panic_condition(len),
923            final(self).pt_cursor.0.invariants(*final(cursor_owner), *final(regions), *final(guards)),
924            old(self).pt_cursor.0.model(*old(cursor_owner)).unmap_spec(len, final(self).pt_cursor.0.model(*final(cursor_owner)), r),
925            final(tlb_model).inv(),
926    )]
927    pub fn unmap(&mut self, len: usize) -> usize {
928        proof {
929            cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
930            cursor_owner.view_preserves_inv();
931        }
932
933        assert_eq!(len % PAGE_SIZE, 0);
934
935        //*** KNOWN BUG: `self.virt_addr() + len` could overflow. For now, assume that it doesn't. ***
936        assume(self.pt_cursor.0.va + len <= usize::MAX);
937
938        assert!(self.virt_addr() + len <= self.pt_cursor.0.barrier_va.end);
939
940        assert(!self.pt_cursor.0.find_next_panic_condition(len));
941        assert(!old(self).pt_cursor.0.find_next_panic_condition(len));
942
943        let end_va = self.virt_addr() + len;
944        let mut num_unmapped: usize = 0;
945
946        let ghost start_va: Vaddr = cursor_owner@.cur_va;
947        // The "adjusted base" accumulates splits: starts as the split-at-boundaries
948        // version of start_mappings and gets updated when take_next splits huge pages.
949        let ghost mut adjusted_base: Set<Mapping> = cursor_owner@.mappings;
950        // Track the set of removed mappings explicitly (not as a VA range filter).
951        let ghost mut removed: Set<Mapping> = Set::empty();
952
953        proof {
954            // end_va <= barrier_va.end == locked_range().end. The cursor invariant
955            // bounds locked_range().end by `vaddr_range_bounds_spec::<C>().1 + 1`,
956            // and for UserPtConfig that evaluates to 2^47.
957            crate::mm::page_table::lemma_vaddr_range_bounds_spec_user();
958            assert(end_va <= 0x0000_8000_0000_0000usize);
959
960            assert((self.pt_cursor.0.va + len) % PAGE_SIZE as int == 0) by (compute);
961            assert(adjusted_base.difference(removed) =~= adjusted_base);
962        }
963
964        #[verus_spec(
965            invariant
966                self.pt_cursor.0.va % PAGE_SIZE == 0,
967                end_va % PAGE_SIZE == 0,
968                end_va <= 0x0000_8000_0000_0000usize,
969                self.pt_cursor.0.invariants(*cursor_owner, *regions, *guards),
970                end_va <= self.pt_cursor.0.barrier_va.end,
971                tlb_model.inv(),
972                start_va <= cursor_owner@.cur_va,
973                // Split-aware invariant: adjusted_base tracks accumulated splits,
974                // removed tracks the explicitly removed set.
975                cursor_owner@.mappings =~= adjusted_base.difference(removed),
976                removed.subset_of(adjusted_base),
977                num_unmapped as nat == removed.len(),
978                removed.finite(),
979                crate::specs::mm::page_table::mapping_set_lemmas::wf_mapping_set(adjusted_base),
980                // Everything removed is in the [start, end) range.
981                forall |m: Mapping| #[trigger] removed.contains(m) ==>
982                    start_va <= m.va_range.start < end_va,
983                // Per-config VA bound: every removed mapping fits within the
984                // user VA space. Sourced from `axiom_view_in_vaddr_range` on
985                // the cursor view prior to removal.
986                forall |m: Mapping| #[trigger] removed.contains(m) ==>
987                    m.va_range.end <= 0x0000_8000_0000_0000_usize as int,
988                // Nothing in [start_va, end_va) with start < cursor_va remains,
989                // unless it is a sub-mapping of a boundary-straddling entry.
990                forall |m: Mapping| #![auto] adjusted_base.contains(m) && !removed.contains(m)
991                    && start_va <= m.va_range.start && m.va_range.start < end_va ==>
992                    m.va_range.start >= cursor_owner@.cur_va
993                    || exists |parent: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(parent)
994                        && parent.va_range.start < start_va
995                        && parent.va_range.start <= m.va_range.start
996                        && m.va_range.end <= parent.va_range.end
997                        && m.pa_range.start == (parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
998                        && m.property == parent.property,
999                start_va == old(cursor_owner)@.cur_va,
1000                old(cursor_owner)@.inv(),
1001                // Locality: old mappings fully outside [start, end) survive in adjusted_base.
1002                // (Straddling mappings may be split — see refinement.)
1003                forall |m: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(m)
1004                    && (m.va_range.end <= start_va || m.va_range.start >= end_va)
1005                    ==> #[trigger] adjusted_base.contains(m),
1006                // Refinement: every mapping in adjusted_base is either from the old view
1007                // or a sub-mapping of an old entry (from boundary splits).
1008                forall |m: Mapping| #[trigger] adjusted_base.contains(m) ==>
1009                    old(cursor_owner)@.mappings.contains(m)
1010                    || exists |parent: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(parent)
1011                        && parent.va_range.start <= m.va_range.start
1012                        && m.va_range.end <= parent.va_range.end
1013                        && m.pa_range.start == (parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1014                        && m.property == parent.property,
1015            invariant_except_break
1016                self.pt_cursor.0.va <= end_va,
1017                self.pt_cursor.0.va < end_va ==> cursor_owner.in_locked_range(),
1018            ensures
1019                self.pt_cursor.0.va >= end_va,
1020            decreases end_va - self.pt_cursor.0.va
1021        )]
1022        loop {
1023            let ghost prev_va: Vaddr = cursor_owner@.cur_va;
1024            let ghost prev_mappings: Set<Mapping> = cursor_owner@.mappings;
1025
1026            let ghost prev_view_inv: bool = cursor_owner@.inv();
1027            proof {
1028                cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1029                cursor_owner.view_preserves_inv();
1030                // Per-config VA bound on prev_mappings — needed for
1031                // preserving the `removed`-end-bound loop invariant.
1032                crate::specs::mm::page_table::cursor::owners::axiom_view_in_vaddr_range::<
1033                    UserPtConfig,
1034                >(cursor_owner);
1035                crate::mm::page_table::lemma_vaddr_range_bounds_spec_user();
1036            }
1037
1038            // SAFETY: It is safe to un-map memory in the userspace.
1039            let Some(frag) = (unsafe {
1040                #[verus_spec(with Tracked(cursor_owner), Tracked(regions), Tracked(guards))]
1041                self.pt_cursor.take_next(end_va - self.virt_addr())
1042            }) else {
1043                proof {
1044                    cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1045                    // At break: take_next returned None, so no mappings in [prev_va, end_va).
1046                    // Any m with start >= prev_va leads to contradiction via the empty filter.
1047                    assert forall|m: Mapping|
1048                        #![auto]
1049                        adjusted_base.contains(m) && !removed.contains(m) && start_va
1050                            <= m.va_range.start && m.va_range.start
1051                            < end_va implies m.va_range.start >= cursor_owner@.cur_va || exists|
1052                        parent: Mapping,
1053                    | #[trigger]
1054                        old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start
1055                            < start_va && parent.va_range.start <= m.va_range.start
1056                            && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
1057                        parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1058                            && m.property == parent.property by {
1059                        if m.va_range.start >= prev_va {
1060                            assert(prev_mappings.filter(
1061                                |m2: Mapping| prev_va <= m2.va_range.start < end_va,
1062                            ).contains(m));
1063                            assert(false);
1064                        }
1065                    };
1066                }
1067                break;
1068            };
1069
1070            let ghost old_adjusted = adjusted_base;
1071            let ghost old_removed = removed;
1072
1073            proof {
1074                cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1075            }
1076
1077            let ghost frag_ghost = frag;
1078
1079            match frag {
1080                PageTableFrag::Mapped { va, item, .. } => {
1081                    let frame = item.frame;
1082                    proof {
1083                        crate::mm::page_table::lemma_vaddr_range_bounds_spec_user();
1084                        // `wf_mapping_set(removed)` from the wf adjusted_base
1085                        // via subset; `va_range.end <= 2^47` for every removed
1086                        // mapping is a loop invariant. Together they give
1087                        // |removed| < usize::MAX, so num_unmapped + 1 fits.
1088                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1089                            adjusted_base,
1090                            removed,
1091                        );
1092                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_mapping_set_cardinality_fits_usize(
1093                        removed);
1094                    }
1095                    assert(num_unmapped < usize::MAX);
1096                    num_unmapped += 1;
1097                    #[verus_spec(with Tracked(tlb_model))]
1098                    self.flusher.issue_tlb_flush_with(TlbFlushOp::Address(va), frame.into_dyn());
1099                },
1100                PageTableFrag::StrayPageTable { pt, va, len, num_frames } => {
1101                    proof {
1102                        let ghost new_removed = old_removed.union(
1103                            prev_mappings.filter(
1104                                |m2: Mapping|
1105                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1106                                        < frag_ghost->StrayPageTable_va
1107                                        + frag_ghost->StrayPageTable_len,
1108                            ),
1109                        );
1110                        assert(new_removed.subset_of(old_adjusted)) by {
1111                            assert forall|m: Mapping|
1112                                new_removed.contains(m) implies old_adjusted.contains(m) by {
1113                                if prev_mappings.contains(m) {
1114                                    // m ∈ prev_mappings = old_adjusted \ old_removed ⊆ old_adjusted.
1115                                }
1116                            };
1117                        };
1118                        vstd::set::axiom_set_union_finite(
1119                            old_removed,
1120                            prev_mappings.filter(
1121                                |m2: Mapping|
1122                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1123                                        < frag_ghost->StrayPageTable_va
1124                                        + frag_ghost->StrayPageTable_len,
1125                            ),
1126                        );
1127                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1128                            old_adjusted,
1129                            new_removed,
1130                        );
1131                        crate::mm::page_table::lemma_vaddr_range_bounds_spec_user();
1132                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_mapping_set_cardinality_fits_usize(
1133                        new_removed);
1134                        // |new_removed| = |old_removed| + |subtree| (disjoint).
1135                        assert(old_removed.disjoint(
1136                            prev_mappings.filter(
1137                                |m2: Mapping|
1138                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1139                                        < frag_ghost->StrayPageTable_va
1140                                        + frag_ghost->StrayPageTable_len,
1141                            ),
1142                        )) by {
1143                            assert forall|m: Mapping|
1144                                old_removed.contains(m) implies !prev_mappings.filter(
1145                                |m2: Mapping|
1146                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1147                                        < frag_ghost->StrayPageTable_va
1148                                        + frag_ghost->StrayPageTable_len,
1149                            ).contains(m) by {
1150                                assert(!prev_mappings.contains(m));
1151                            };
1152                        };
1153                        vstd::set::axiom_set_intersect_finite::<Mapping>(
1154                            prev_mappings,
1155                            Set::new(
1156                                |m2: Mapping|
1157                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1158                                        < frag_ghost->StrayPageTable_va
1159                                        + frag_ghost->StrayPageTable_len,
1160                            ),
1161                        );
1162                        vstd::set_lib::lemma_set_disjoint_lens(
1163                            old_removed,
1164                            prev_mappings.filter(
1165                                |m2: Mapping|
1166                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1167                                        < frag_ghost->StrayPageTable_va
1168                                        + frag_ghost->StrayPageTable_len,
1169                            ),
1170                        );
1171                        assert(num_unmapped + num_frames < usize::MAX);
1172                    }
1173                    num_unmapped += num_frames;
1174                    proof {
1175                        assert(0x0000_8000_0000_0000usize < KERNEL_VADDR_RANGE.end as usize)
1176                            by (compute_only);
1177                        assert(va + len <= KERNEL_VADDR_RANGE.end as usize);
1178                        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_va_plus_page_size_no_overflow(
1179                        va, len);
1180                    }
1181                    #[verus_spec(with Tracked(tlb_model))]
1182                    self.flusher.issue_tlb_flush_with(TlbFlushOp::Range(va..va + len), pt);
1183                },
1184            }
1185
1186            proof {
1187                let ghost mm = match frag_ghost {
1188                    PageTableFrag::Mapped { va: fv, item: fi, .. } => CursorView::<
1189                        UserPtConfig,
1190                    >::item_into_mapping(fv, fi),
1191                    _ => arbitrary(),
1192                };
1193                let ghost sv = CursorView::<UserPtConfig> {
1194                    cur_va: match frag_ghost {
1195                        PageTableFrag::Mapped { va: fv, .. } => fv as Vaddr,
1196                        _ => 0,
1197                    },
1198                    mappings: prev_mappings,
1199                    phantom: PhantomData,
1200                };
1201                let ghost sm = sv.split_while_huge(mm.page_size).mappings;
1202                let ghost is_mapped = frag_ghost is Mapped;
1203                let ghost subtree = match frag_ghost {
1204                    PageTableFrag::StrayPageTable { va: fv, len: fl, .. } => prev_mappings.filter(
1205                        |m2: Mapping| fv <= m2.va_range.start < fv + fl,
1206                    ),
1207                    _ => Set::empty(),
1208                };
1209
1210                // Update ghost tracking variables.
1211                match frag_ghost {
1212                    PageTableFrag::StrayPageTable { .. } => {
1213                        removed = old_removed.union(subtree);
1214                    },
1215                    PageTableFrag::Mapped { .. } => {
1216                        adjusted_base = sm.union(old_removed);
1217                        removed = old_removed.union(set![mm]);
1218                    },
1219                }
1220
1221                // Mapped-case setup: establish split_while_huge properties once.
1222                if is_mapped {
1223                    assert(sv.cur_va < 0x0000_8000_0000_0000usize);
1224                    assert forall|m: Mapping, x: Mapping| #[trigger]
1225                        prev_mappings.contains(m) && #[trigger] old_removed.contains(
1226                            x,
1227                        ) implies Mapping::disjoint_vaddrs(m, x) by {
1228                        assert(old_adjusted.contains(m));
1229                        assert(old_adjusted.contains(x));
1230                    };
1231                    sv.split_while_huge_disjoint(mm.page_size, old_removed);
1232                    sv.lemma_split_while_huge_preserves_inv(mm.page_size);
1233                }
1234                assert forall|m: Mapping| #[trigger] removed.contains(m) implies m.va_range.end
1235                    <= 0x0000_8000_0000_0000_usize as int by {
1236                    if !old_removed.contains(m) {
1237                        if is_mapped {
1238                            assert(m == mm);
1239                            sv.split_while_huge_refinement(mm.page_size, mm);
1240                            if !prev_mappings.contains(mm) {
1241                                let parent = choose|p: Mapping| #[trigger]
1242                                    prev_mappings.contains(p) && p.va_range.start
1243                                        <= mm.va_range.start && mm.va_range.end <= p.va_range.end
1244                                        && mm.pa_range.start == (p.pa_range.start + (
1245                                    mm.va_range.start - p.va_range.start)) as Paddr && mm.property
1246                                        == p.property;
1247                            }
1248                        } else {
1249                            assert(prev_mappings.contains(m));
1250                        }
1251                    }
1252                };
1253
1254                // Prove |removed| tracking (disjointness + cardinality).
1255
1256                match frag_ghost {
1257                    PageTableFrag::StrayPageTable { .. } => {
1258                        assert(old_removed.disjoint(subtree)) by {
1259                            assert forall|e: Mapping|
1260                                old_removed.contains(e) implies !subtree.contains(e) by {};
1261                        };
1262                        vstd::set::axiom_set_intersect_finite::<Mapping>(
1263                            prev_mappings,
1264                            Set::new(|m2: Mapping| subtree.contains(m2)),
1265                        );
1266                        vstd::set_lib::lemma_set_disjoint_lens(old_removed, subtree);
1267                        assert(removed =~= old_removed + subtree);
1268                    },
1269                    PageTableFrag::Mapped { .. } => {
1270                        assert(old_removed.disjoint(set![mm])) by {
1271                            assert forall|e: Mapping| #[trigger]
1272                                old_removed.contains(e) implies !set![mm].contains(e) by {};
1273                        };
1274                        vstd::set::axiom_set_insert_finite(Set::<Mapping>::empty(), mm);
1275                        vstd::set_lib::lemma_set_disjoint_lens(old_removed, set![mm]);
1276                        assert(removed =~= old_removed + set![mm]);
1277                        vstd::set_lib::lemma_set_empty_equivalency_len(Set::<Mapping>::empty());
1278                        assert(set![mm] =~= Set::<Mapping>::empty().insert(mm));
1279                        vstd::set::axiom_set_insert_len(Set::<Mapping>::empty(), mm);
1280                    },
1281                }
1282
1283                // Maintain wf_mapping_set(adjusted_base) — only changes in Mapped case.
1284                if is_mapped {
1285                    vstd::set::axiom_set_union_finite(sm, old_removed);
1286                    crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1287                        old_adjusted,
1288                        old_removed,
1289                    );
1290                    assert forall|m: Mapping, n: Mapping| #[trigger]
1291                        sm.contains(m) && #[trigger] old_removed.contains(n) implies m.va_range.end
1292                        <= n.va_range.start || n.va_range.end <= m.va_range.start by {
1293                        sv.split_while_huge_refinement(mm.page_size, m);
1294                        assert(!prev_mappings.contains(n));
1295                        if prev_mappings.contains(m) {
1296                        } else {
1297                            let p = choose|p: Mapping| #[trigger]
1298                                prev_mappings.contains(p) && p.va_range.start <= m.va_range.start
1299                                    && m.va_range.end <= p.va_range.end && m.pa_range.start == (
1300                                p.pa_range.start + (m.va_range.start - p.va_range.start)) as Paddr
1301                                    && m.property == p.property;
1302                            assert(old_adjusted.contains(p));
1303                        }
1304                    };
1305                    crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_union(
1306                        sm,
1307                        old_removed,
1308                    );
1309                }
1310                // Maintain mappings =~= adjusted_base \ removed.
1311
1312                assert forall|e: Mapping| #[trigger]
1313                    adjusted_base.difference(removed).contains(e)
1314                        <==> cursor_owner@.mappings.contains(e) by {};
1315                assert(cursor_owner@.mappings =~= adjusted_base.difference(removed));
1316
1317                assert(removed.subset_of(adjusted_base)) by {
1318                    assert forall|e: Mapping| #[trigger]
1319                        removed.contains(e) implies adjusted_base.contains(e) by {};
1320                };
1321
1322                // Maintain: not-yet-removed mappings in [start, end) are either
1323                // ahead of the cursor or sub-mappings of a boundary-straddling parent.
1324                assert forall|m: Mapping|
1325                    #![auto]
1326                    adjusted_base.contains(m) && !removed.contains(m) && start_va
1327                        <= m.va_range.start && m.va_range.start < end_va implies m.va_range.start
1328                    >= cursor_owner@.cur_va || exists|parent: Mapping| #[trigger]
1329                    old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start < start_va
1330                        && parent.va_range.start <= m.va_range.start && m.va_range.end
1331                        <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
1332                    m.va_range.start - parent.va_range.start)) as Paddr && m.property
1333                        == parent.property by {
1334                    if m.va_range.start < cursor_owner@.cur_va {
1335                        if m.va_range.start >= prev_va {
1336                            // m was just processed — contradiction via empty filtered sets.
1337                            match frag_ghost {
1338                                PageTableFrag::StrayPageTable { va: frag_va, .. } => {
1339                                    if m.va_range.start >= frag_va {
1340                                        assert(cursor_owner@.mappings.filter(
1341                                            |m2: Mapping|
1342                                                frag_va <= m2.va_range.start < self.pt_cursor.0.va,
1343                                        ).contains(m));
1344                                    } else {
1345                                        assert(prev_mappings.filter(
1346                                            |m2: Mapping| prev_va <= m2.va_range.start < frag_va,
1347                                        ).contains(m));
1348                                    }
1349                                },
1350                                PageTableFrag::Mapped { va: frag_va, .. } => {
1351                                    if m.va_range.start >= (frag_va as usize) {
1352                                        assert(cursor_owner@.mappings.filter(
1353                                            |m2: Mapping|
1354                                                (frag_va as usize) <= m2.va_range.start
1355                                                    < self.pt_cursor.0.va,
1356                                        ).contains(m));
1357                                    } else {
1358                                        assert(prev_mappings.filter(
1359                                            |m2: Mapping|
1360                                                prev_va <= m2.va_range.start < (frag_va as usize),
1361                                        ).contains(m));
1362                                    }
1363                                },
1364                            }
1365                            assert(false);
1366                        } else if is_mapped {
1367                            // m.start < prev_va, m ∈ sm \ old_removed.
1368                            assert(sm.contains(m));
1369                            sv.split_while_huge_refinement(mm.page_size, m);
1370                            if prev_mappings.contains(m) {
1371                                // m ∈ old_adjusted \ old_removed — previous invariant applies directly.
1372                            } else {
1373                                // m is a sub-mapping of some p ∈ prev_mappings.
1374                                let p = choose|p: Mapping| #[trigger]
1375                                    prev_mappings.contains(p) && p.va_range.start
1376                                        <= m.va_range.start && m.va_range.end <= p.va_range.end
1377                                        && m.pa_range.start == (p.pa_range.start + (m.va_range.start
1378                                        - p.va_range.start)) as Paddr && m.property == p.property;
1379                                assert(old_adjusted.contains(p) && !old_removed.contains(p));
1380                                if p.va_range.start < start_va {
1381                                    // p itself or its ancestor is the boundary parent.
1382                                    if !old(cursor_owner)@.mappings.contains(p) {
1383                                        let orig = choose|orig: Mapping| #[trigger]
1384                                            old(cursor_owner)@.mappings.contains(orig)
1385                                                && orig.va_range.start <= p.va_range.start
1386                                                && p.va_range.end <= orig.va_range.end
1387                                                && p.pa_range.start == (orig.pa_range.start + (
1388                                            p.va_range.start - orig.va_range.start)) as Paddr
1389                                                && p.property == orig.property;
1390                                        assert(orig.inv());
1391                                        assert(m.inv());
1392                                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1393                                        m, p, orig);
1394                                    }
1395                                } else if p.va_range.start >= end_va {
1396                                    assert(false);  // p.start <= m.start < end_va, contradiction.
1397                                } else {
1398                                    // start_va <= p.start < end_va, p.start < prev_va.
1399                                    // Previous invariant gives boundary ancestor orig.
1400                                    let orig = choose|orig: Mapping| #[trigger]
1401                                        old(cursor_owner)@.mappings.contains(orig)
1402                                            && orig.va_range.start < start_va && orig.va_range.start
1403                                            <= p.va_range.start && p.va_range.end
1404                                            <= orig.va_range.end && p.pa_range.start == (
1405                                        orig.pa_range.start + (p.va_range.start
1406                                            - orig.va_range.start)) as Paddr && p.property
1407                                            == orig.property;
1408                                    assert(orig.inv());
1409                                    assert(m.inv());
1410                                    crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1411                                    m, p, orig);
1412                                }
1413                            }
1414                        }
1415                    }
1416                };
1417
1418                // Maintain: old mappings outside [start, end) survive in adjusted_base.
1419                if is_mapped {
1420                    assert forall|m: Mapping|
1421                        old(cursor_owner)@.mappings.contains(m) && (m.va_range.end <= start_va
1422                            || m.va_range.start
1423                            >= end_va) implies #[trigger] adjusted_base.contains(m) by {
1424                        if m.va_range.end <= start_va {
1425                            assert(m.inv());
1426                        }
1427                        sv.split_while_huge_locality(mm.page_size, m);
1428                    };
1429
1430                    // Maintain: refinement — every mapping in adjusted_base comes from
1431                    // old mappings or is a sub-mapping of one.
1432                    assert forall|m: Mapping| #[trigger] adjusted_base.contains(m) implies old(
1433                        cursor_owner,
1434                    )@.mappings.contains(m) || exists|parent: Mapping| #[trigger]
1435                        old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start
1436                            <= m.va_range.start && m.va_range.end <= parent.va_range.end
1437                            && m.pa_range.start == (parent.pa_range.start + (m.va_range.start
1438                            - parent.va_range.start)) as Paddr && m.property == parent.property by {
1439                        if !old_removed.contains(m) {
1440                            sv.split_while_huge_refinement(mm.page_size, m);
1441                            if !prev_mappings.contains(m) {
1442                                let p = choose|p: Mapping| #[trigger]
1443                                    prev_mappings.contains(p) && p.va_range.start
1444                                        <= m.va_range.start && m.va_range.end <= p.va_range.end
1445                                        && m.pa_range.start == (p.pa_range.start + (m.va_range.start
1446                                        - p.va_range.start)) as Paddr && m.property == p.property;
1447                                assert(old_adjusted.contains(p));
1448                                if !old(cursor_owner)@.mappings.contains(p) {
1449                                    let orig = choose|orig: Mapping| #[trigger]
1450                                        old(cursor_owner)@.mappings.contains(orig)
1451                                            && orig.va_range.start <= p.va_range.start
1452                                            && p.va_range.end <= orig.va_range.end
1453                                            && p.pa_range.start == (orig.pa_range.start + (
1454                                        p.va_range.start - orig.va_range.start)) as Paddr
1455                                            && p.property == orig.property;
1456                                    assert(orig.inv());
1457                                    assert(m.inv());
1458                                    crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1459                                    m, p, orig);
1460                                }
1461                            }
1462                        }
1463                    }
1464                };
1465            }
1466        }
1467        proof {
1468            cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1469
1470            let old_view = old(self).pt_cursor.0.model(*old(cursor_owner));
1471            let new_view = self.pt_cursor.0.model(*cursor_owner);
1472
1473            // Bridge from loop invariant to unmap_spec.
1474            let start = old_view.cur_va;
1475            let end = (old_view.cur_va + len) as Vaddr;
1476
1477            assert(new_view.cur_va >= end);
1478
1479            assert forall|m: Mapping|
1480                #![auto]
1481                old_view.mappings.contains(m) && (m.va_range.end <= start || m.va_range.start
1482                    >= end) implies new_view.mappings.contains(m) by {
1483                assert(adjusted_base.contains(m));
1484                if m.va_range.end <= start {
1485                    assert(m.inv());
1486                }
1487            };
1488
1489            assert forall|m: Mapping|
1490                new_view.mappings.contains(m) && start <= m.va_range.start < end implies exists|
1491                parent: Mapping,
1492            | #[trigger]
1493                old_view.mappings.contains(parent) && parent.va_range.start < start
1494                    && parent.va_range.start <= m.va_range.start && m.va_range.end
1495                    <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
1496                m.va_range.start - parent.va_range.start)) as Paddr && m.property
1497                    == parent.property by {};
1498
1499            assert forall|m: Mapping|
1500                new_view.mappings.contains(m) implies old_view.mappings.contains(m) || exists|
1501                parent: Mapping,
1502            | #[trigger]
1503                old_view.mappings.contains(parent) && parent.va_range.start <= m.va_range.start
1504                    && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
1505                parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1506                    && m.property == parent.property by {};
1507        }
1508
1509        #[verus_spec(with Tracked(tlb_model))]
1510        self.flusher.dispatch_tlb_flush();
1511
1512        num_unmapped
1513    }
1514
1515    /// Applies the operation to the next slot of mapping within the range.
1516    ///
1517    /// The range to be found in is the current virtual address with the
1518    /// provided length.
1519    ///
1520    /// The function stops and yields the actually protected range if it has
1521    /// actually protected a page, no matter if the following pages are also
1522    /// required to be protected.
1523    ///
1524    /// It also makes the cursor moves forward to the next page after the
1525    /// protected one. If no mapped pages exist in the following range, the
1526    /// cursor will stop at the end of the range and return [`None`].
1527    ///
1528    /// Note that it will **NOT** flush the TLB after the operation. Please
1529    /// make the decision yourself on when and how to flush the TLB using
1530    /// [`Self::flusher`].
1531    ///
1532    /// # Verified Properties
1533    /// ## Preconditions
1534    /// - **Safety Invariants**: The page table cursor safety invariants
1535    ///   ([`invariants`](crate::mm::page_table::Cursor::invariants)) and the
1536    ///   meta-region invariants must hold before the call.
1537    /// - The cursor must be within the locked range and below the guard level.
1538    /// - The current entry must be a mapped frame (not absent or a page table node).
1539    /// - **Liveness**: The length must be page-aligned and within the remaining cursor range.
1540    /// ## Postconditions
1541    /// - **Correctness**: If the metadata region was well-formed before the call, it will be
1542    ///   well-formed after.
1543    /// ## Panics
1544    /// Panics if the length is longer than the remaining range of the cursor.
1545    /// ## Safety
1546    /// - From a soundness perspective changing a userspace page's `prop` field is safe.
1547    #[verus_spec(r =>
1548        with
1549            Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
1550            Tracked(regions): Tracked<&mut MetaRegionOwners>,
1551            Tracked(guards): Tracked<&mut Guards<'a>>,
1552        requires
1553            old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
1554            forall |p: PageProperty| op.requires((p,)),
1555            // POTENTIALLY UNSOUND PATCH: trackedness preservation. For UserPtConfig
1556            // this is trivially true (tracked is constant). See `Entry::protect`.
1557            forall |pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty| #![auto]
1558                op.ensures((p_in,), p_out) ==>
1559                    UserPtConfig::tracked(UserPtConfig::item_from_raw_spec(pa, level, p_out))
1560                    == UserPtConfig::tracked(UserPtConfig::item_from_raw_spec(pa, level, p_in)),
1561
1562            old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
1563        ensures
1564            !old(self).pt_cursor.0.find_next_panic_condition(len),
1565            final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
1566            final(self).pt_cursor.0.barrier_va == old(self).pt_cursor.0.barrier_va,
1567    )]
1568    pub fn protect_next(
1569        &mut self,
1570        len: usize,
1571        op: impl FnOnce(PageProperty) -> PageProperty,
1572    ) -> Option<Range<Vaddr>> {
1573        // SAFETY: It is safe to protect memory in the userspace.
1574        unsafe {
1575            #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
1576            self.pt_cursor.protect_next(len, op)
1577        }
1578    }
1579}
1580
1581/*cpu_local_cell! {
1582    /// The `Arc` pointer to the activated VM space on this CPU. If the pointer
1583    /// is NULL, it means that the activated page table is merely the kernel
1584    /// page table.
1585    // TODO: If we are enabling ASID, we need to maintain the TLB state of each
1586    // CPU, rather than merely the activated `VmSpace`. When ASID is enabled,
1587    // the non-active `VmSpace`s can still have their TLB entries in the CPU!
1588    static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null();
1589}*/
1590
1591/*#[cfg(ktest)]
1592pub(super) fn get_activated_vm_space() -> *const VmSpace {
1593    ACTIVATED_VM_SPACE.load()
1594}*/
1595
1596/// The configuration for user page tables.
1597#[derive(Clone, Debug)]
1598pub struct UserPtConfig {}
1599
1600/// The item that can be mapped into the [`VmSpace`].
1601pub struct MappedItem {
1602    pub frame: UFrame,
1603    pub prop: PageProperty,
1604}
1605
1606#[verus_verify]
1607impl RCClone for MappedItem {
1608    open spec fn clone_requires(self, perm: MetaRegionOwners) -> bool {
1609        self.frame.clone_requires(perm)
1610    }
1611
1612    open spec fn clone_ensures(
1613        self,
1614        old_perm: MetaRegionOwners,
1615        new_perm: MetaRegionOwners,
1616        res: Self,
1617    ) -> bool {
1618        self.frame.clone_ensures(old_perm, new_perm, res.frame)
1619    }
1620
1621    fn clone(&self, Tracked(perm): Tracked<&mut MetaRegionOwners>) -> (res: Self) {
1622        let frame = self.frame.clone(Tracked(perm));
1623        Self { frame, prop: self.prop }
1624    }
1625}
1626
1627// SAFETY: `item_into_raw` and `item_from_raw` are implemented correctly,
1628unsafe impl PageTableConfig for UserPtConfig {
1629    open spec fn TOP_LEVEL_INDEX_RANGE_spec() -> Range<usize> {
1630        0..256
1631    }
1632
1633    open spec fn TOP_LEVEL_CAN_UNMAP_spec() -> (b: bool) {
1634        true
1635    }
1636
1637    fn TOP_LEVEL_INDEX_RANGE() -> Range<usize> {
1638        0..256
1639    }
1640
1641    fn TOP_LEVEL_CAN_UNMAP() -> (b: bool) {
1642        true
1643    }
1644
1645    type E = PageTableEntry;
1646
1647    type C = PagingConsts;
1648
1649    type Item = MappedItem;
1650
1651    open spec fn item_into_raw_spec(item: Self::Item) -> (Paddr, PagingLevel, PageProperty) {
1652        (item.frame.paddr(), 1, item.prop)
1653    }
1654
1655    #[verifier::external_body]
1656    fn item_into_raw(item: Self::Item) -> (Paddr, PagingLevel, PageProperty) {
1657        let MappedItem { frame, prop } = item;
1658        let level = frame.map_level();
1659        let paddr = frame.into_raw();
1660        (paddr, level, prop)
1661    }
1662
1663    uninterp spec fn item_from_raw_spec(
1664        paddr: Paddr,
1665        level: PagingLevel,
1666        prop: PageProperty,
1667    ) -> Self::Item;
1668
1669    #[verifier::external_body]
1670    unsafe fn item_from_raw(paddr: Paddr, level: PagingLevel, prop: PageProperty) -> Self::Item {
1671        let frame = unsafe { UFrame::from_raw(paddr) };
1672        MappedItem { frame, prop }
1673    }
1674
1675    axiom fn axiom_nr_subpage_per_huge_eq_nr_entries();
1676
1677    axiom fn axiom_pte_size_eq_size_of();
1678
1679    axiom fn axiom_pte_walk_fills_page();
1680
1681    axiom fn axiom_top_level_index_range_within_nr_entries();
1682
1683    axiom fn axiom_pte_align_divides_size();
1684
1685    axiom fn item_roundtrip(item: Self::Item, paddr: Paddr, level: PagingLevel, prop: PageProperty);
1686
1687    open spec fn tracked(_item: Self::Item) -> bool {
1688        // Every UserPt item is a ref-counted UFrame.
1689        true
1690    }
1691
1692    open spec fn item_well_formed(item: Self::Item) -> bool {
1693        item.frame.inv()
1694    }
1695
1696    proof fn item_from_raw_well_formed(pa: Paddr, level: PagingLevel, prop: PageProperty) {
1697        broadcast use crate::mm::frame::meta::mapping::group_page_meta;
1698        // Derive `frame.inv()` from the structural-shape axiom + soundness lemmas.
1699
1700        Self::item_from_raw_spec_frame_ptr(pa, level, prop);
1701        let item = Self::item_from_raw_spec(pa, level, prop);
1702        assert(item.frame.ptr.addr() == crate::mm::frame::meta::mapping::frame_to_meta(pa));
1703        // frame.inv() unfolds to `addr % META_SLOT_SIZE == 0` and addr in
1704        // FRAME_METADATA_RANGE. Both follow from `lemma_frame_to_meta_soundness`.
1705        assert(item.frame.inv());
1706    }
1707
1708    proof fn clone_ensures_concrete(
1709        item: Self::Item,
1710        pa: Paddr,
1711        old_regions: MetaRegionOwners,
1712        new_regions: MetaRegionOwners,
1713        res: Self::Item,
1714    ) {
1715        // `MappedItem::clone_ensures` unfolds to `item.frame.clone_ensures(...,
1716        // res.frame)`, which delivers per-field facts at `frame_to_index(meta_to_frame(
1717        // item.frame.ptr.addr()))`. Bridge that index to `frame_to_index(pa)` via
1718        // `pa == item.frame.paddr() == meta_to_frame(item.frame.ptr.addr())`.
1719        use crate::mm::frame::meta::mapping::{frame_to_index, meta_to_frame};
1720        let frame_idx = frame_to_index(meta_to_frame(item.frame.ptr.addr()));
1721        assert(pa == item.frame.paddr());
1722        assert(frame_to_index(pa) == frame_idx);
1723        // The MappedItem clone_ensures unfolds to its frame's clone_ensures.
1724        // Verus needs `item.clone_ensures` (a trait method) revealed via the impl.
1725        assert(<MappedItem as RCClone>::clone_ensures(item, old_regions, new_regions, res));
1726        assert(item.frame.clone_ensures(old_regions, new_regions, res.frame));
1727        assert(new_regions.slot_owners[frame_idx].inner_perms.ref_count.value()
1728            == old_regions.slot_owners[frame_idx].inner_perms.ref_count.value() + 1);
1729        assert(forall|i: usize|
1730            i != frame_idx ==> #[trigger] new_regions.slot_owners[i] == old_regions.slot_owners[i]);
1731        // Canonical: the cloned frame minted one obligation at its slot.
1732        assert(new_regions.frame_obligations =~= old_regions.frame_obligations.insert(frame_idx));
1733    }
1734
1735    proof fn clone_requires_concrete(
1736        item: Self::Item,
1737        pa: Paddr,
1738        level: PagingLevel,
1739        prop: PageProperty,
1740        regions: MetaRegionOwners,
1741    ) {
1742        // `MappedItem::clone_requires` unfolds to `item.frame.clone_requires(regions)`.
1743        // The trait precondition delivers all the slot facts at `frame_to_index(pa)`.
1744        // We bridge:
1745        //   (1) `item.frame.inv()` — discharged via `item_from_raw_well_formed`
1746        //       (the trait-level structural well-formedness method).
1747        //   (2) `frame_to_index(meta_to_frame(item.frame.ptr.addr())) == frame_to_index(pa)`
1748        //       from `pa == item.frame.paddr()` (UserPtConfig::item_into_raw_spec).
1749        use crate::mm::frame::meta::mapping::{frame_to_index, meta_to_frame};
1750        Self::item_roundtrip(item, pa, level, prop);
1751        assert(item.frame.paddr() == pa);
1752        assert(meta_to_frame(item.frame.ptr.addr()) == pa);
1753        assert(frame_to_index(meta_to_frame(item.frame.ptr.addr())) == frame_to_index(pa));
1754        Self::item_from_raw_well_formed(pa, level, prop);
1755        // `Self::item_well_formed(item)` unfolds to `item.frame.inv()`.
1756        assert(item.frame.inv());
1757    }
1758}
1759
1760impl UserPtConfig {
1761    /// Structural shape of `item_from_raw_spec` for UserPtConfig: the reconstructed
1762    /// item's frame has its pointer at `frame_to_meta(pa)`. Mirrors the exec body's
1763    /// `UFrame::from_raw(pa)` call whose ensures gives this address shape.
1764    pub axiom fn item_from_raw_spec_frame_ptr(pa: Paddr, level: PagingLevel, prop: PageProperty)
1765        requires
1766            crate::mm::frame::meta::has_safe_slot(pa),
1767        ensures
1768            UserPtConfig::item_from_raw_spec(pa, level, prop).frame.ptr.addr()
1769                == crate::mm::frame::meta::mapping::frame_to_meta(pa),
1770    ;
1771}
1772
1773} // verus!