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