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