Mmap.lean
Declarations on this page (22)
- def unmapWritable
- def unmapNonFrozen
- def setPermNonFrozen
- def PAGE_SIZE
- def pageAlignUp
- def MMAP_PROT_NONE
- def MMAP_PROT_RW
- def MMAP_FLAGS_PRIVATE_ANON
-
def MMAP_
FLAGS_ PRIVATE_ ANON_ STACK -
def MMAP_
FLAGS_ PRIVATE_ ANON_ FIXED - def MMAP_MIN_ADDR
- def SYS_BRK
- def SYS_MUNMAP
- def SYS_MMAP
- def findMappedByte
- def findUnmappedAreaLoop
- def findUnmappedArea
- def sanitizeMmapAddr
- def execMmap
- def execMunmap
- def BRK_GUARD_GAP
- def execBrk
Linux Virtual Memory brk, mmap, & munmap Syscalls (Arm64.)
-
Layer / Role:
Arm64Trusted Computing Base (Role::Spec, definitions only, 0 theorems). -
Linux
7.3Upstream Sources:-
mm/(mmap.c:115 SYSCALL_DEFINE1(brk, unsigned long, brk)->check_brk_limits->do_brk_flags/do_brk_munmap) -
mm/(mmap.c:1661 SYSCALL_DEFINE6(mmap_pgoff)/ksys_mmap_pgoff->vm_mmap_pgoff->do_mmap) -
mm/(vma.c:1718 do_vmi_munmap/vm_munmap, invoked frommm/mmap.c SYSCALL_DEFINE2(munmap)) -
include/(uapi/ asm-generic/ mman-common.h:10 PROT_NONE = 0x0,PROT_READ | PROT_WRITE = 0x3,MAP_PRIVATE | MAP_ANONYMOUS = 0x22,MAP_STACK = 0x20000,MAP_FIXED= 0x10) -
include/(linux/ mman.h:158 calc_vm_flag_bits:MAP_STACKmaps to advisoryVM_NOHUGEPAGE)
-
-
Purpose & Key Exports:
Memory.,unmapWritable Memory.,unmapNonFrozen Memory.,setPermNonFrozen PAGE_SIZE,USER_VA_LIMIT,pageAlignUp,MMAP_PROT_NONE,MMAP_PROT_RW,MMAP_FLAGS_PRIVATE_ANON,MMAP_,FLAGS_ PRIVATE_ ANON_ STACK MAP_FIXED,MMAP_,FLAGS_ PRIVATE_ ANON_ FIXED MMAP_MIN_ADDR,SYS_BRK(214),SYS_MUNMAP(215),SYS_MMAP(222),findMappedByte,findUnmappedAreaLoop,findUnmappedArea,sanitizeMmapAddr,execMmap,execMunmap, andexecBrk.
@[expose] public sectionnamespace Arm64namespace Memorydef unmapWritable
Unmap the writable bytes of [base, base + len) (they lose their cell); other bytes are unchanged. Used when brk shrinks the heap (do_brk_munmap in mm/). Never touches frozen bytes. Note: if a heap page was set to Perm. via mprotect, unmapWritable conservatively leaves it some Perm. (so later canMap checks treat it as still reserved).
def unmapNonFrozen
Unmap all non-frozen bytes of [base, base + len) (they lose their cell); frozen bytes (some Perm., some Perm.) and bytes outside the range are unchanged. Used for munmap (do_vmi_munmap in mm/), which unmaps both Perm. and Perm. pages while leaving already-unmapped holes unmapped. Never touches frozen bytes.
def setPermNonFrozen
Set the permission of all non-frozen mapped bytes in [base, base + len) to p's protection bits, preserving their byte values, lazyFree state, and anon state; frozen and unmapped bytes (and bytes outside the range) are unchanged. Used for mprotect transitions between Perm. and Perm. (mm/ change_protection, which updates PTE protection bits without clearing folio_test_lazyfree or changing VMA vm_ops). Never touches frozen bytes.
end Memorydef PAGE_SIZE
Linux page size on AArch64 (4 KiB granule, arch/).
def PAGE_SIZE : Nat := 4096def pageAlignUp
Round n up to a multiple of PAGE_SIZE.
def MMAP_PROT_NONE
mmap / mprotect prot argument PROT_NONE (0x0, include/).
def MMAP_PROT_NONE : BitVec 64 := 0x0#64def MMAP_PROT_RW
mmap / mprotect prot argument PROT_READ | PROT_WRITE (0x3, include/).
def MMAP_PROT_RW : BitVec 64 := 0x3#64def MMAP_FLAGS_PRIVATE_ANON
mmap flags argument MAP_PRIVATE | MAP_ANONYMOUS (0x22, include/).
def MMAP_FLAGS_PRIVATE_ANON : BitVec 64 := 0x22#64
def MMAP_
mmap flags argument MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK (0x20022, include/; MAP_STACK = 0x20000 maps only to advisory VM_NOHUGEPAGE in include/).
def MMAP_FLAGS_PRIVATE_ANON_STACK : BitVec 64 := 0x20022#641 declaration not shown (lines 88–89): theorems, which Lean checks, or definitions that no headline statement uses
Arm64.
def MMAP_
mmap flags argument MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED (0x32 = MMAP_FLAGS_PRIVATE_ANON ||| MAP_FIXED).
def MMAP_FLAGS_PRIVATE_ANON_FIXED : BitVec 64 := 0x32#64def MMAP_MIN_ADDR
Lowest address at which the model starts any mmap / mremap mapping (65536): the MAP_FIXED lower bound in execMmap, the floor of oracle candidates accepted by sanitizeMmapAddr, and the start of the findUnmappedArea gap search. Linux refuses lower addresses to processes without CAP_SYS_RAWIO (cap_mmap_addr in security/ checks dac_mmap_min_addr, the vm. sysctl; CONFIG_LSM_MMAP_MIN_ADDR adds a second bound). 65536 is the value Debian, Ubuntu and Fedora set and the default CONFIG_LSM_MMAP_MIN_ADDR; the model assumes the effective bound is exactly this value (MAP_FIXED below it fails closed, and non-fixed placements never land below it).
def MMAP_MIN_ADDR : Nat := 65536def SYS_BRK
Linux AArch64 brk syscall number (__NR_brk = 214, include/, mm/).
def SYS_BRK : BitVec 64 := 214#64def SYS_MUNMAP
Linux AArch64 munmap syscall number (__NR_munmap = 215, include/, mm/).
def SYS_MUNMAP : BitVec 64 := 215#64def SYS_MMAP
Linux AArch64 mmap syscall number (__NR_mmap = 222, include/, mm/).
def SYS_MMAP : BitVec 64 := 222#64def findMappedByte
Smallest byte offset curOff ≤ off < curOff + rem such that (m.BitVec. 64 off)).mapped = true, or none if [base + curOff, base + curOff + rem) is completely unmapped. Tail-recursive so large probes run in constant stack space.
def findMappedByte (m : Memory) (base : BitVec 64) (curOff : Nat) : Nat → Option Nat
| 0 => none
| rem + 1 =>
if (m.perm (base + BitVec.ofNat 64 curOff)).mapped then some curOff
else findMappedByte m base (curOff + 1) remdef findUnmappedAreaLoop
Low-to-high page-aligned gap search over [addr, USER_VA_LIMIT) (vm_unmapped_area in mm/), advancing past the page containing any mapped byte on collision.
def findUnmappedAreaLoop (m : Memory) (size : Nat) : Nat → Nat → Option (BitVec 64)
| 0, _ => none
| fuel + 1, addr =>
if !decide (addr + size ≤ USER_VA_LIMIT) then
none
else
let base := BitVec.ofNat 64 addr
match findMappedByte m base 0 size with
| none => some base
| some off =>
let nextPage := ((addr + off) / PAGE_SIZE + 1) * PAGE_SIZE
findUnmappedAreaLoop m size fuel nextPagedef findUnmappedArea
Lowest page-aligned unmapped range [base, base + size) within [MMAP_MIN_ADDR, USER_VA_LIMIT) (arch_get_unmapped_area / vm_unmapped_area in mm/), or none when size = 0, size is not page-aligned, or no free gap of size bytes exists.
def findUnmappedArea (m : Memory) (size : Nat) : Option (BitVec 64) :=
if size == 0 || size % PAGE_SIZE != 0 then
none
else
findUnmappedAreaLoop m size ((USER_VA_LIMIT - MMAP_MIN_ADDR) / PAGE_SIZE + 1) MMAP_MIN_ADDRdef sanitizeMmapAddr
Sanitize an oracle mmap / mremap placement candidate cand for a page-aligned request of size bytes (size > 0, size % PAGE_SIZE = 0, mm/ get_unmapped_area -> security_mmap_addr):
-
if
MMAP_MIN_ADDR≤ cand.toNat ,candisPAGE_SIZE-aligned, cand.toNat + size ≤USER_VA_LIMIT, andm., acceptscanMap cand size = true canddirectly; -
otherwise substitutes
findUnmappedAream size, returningnoneonly when no unmapped page-aligned range ofsizebytes fits in [MMAP_MIN_ADDR,USER_VA_LIMIT).
def sanitizeMmapAddr (m : Memory) (cand : BitVec 64) (size : Nat) : Option (BitVec 64) :=
if size == 0 || size % PAGE_SIZE != 0 then
none
else if decide (MMAP_MIN_ADDR ≤ cand.toNat) && cand.toNat % PAGE_SIZE == 0 &&
decide (cand.toNat + size ≤ USER_VA_LIMIT) && m.canMap cand size then
some cand
else
findUnmappedArea m sizedef execMmap
Anonymous private mmap(addr, len, prot, flags, fd, 0) (__NR_mmap = 222, mm/ ksys_mmap_pgoff -> do_mmap) with prot ∈ {PROT_NONE (0), PROT_READ | PROT_WRITE (3)} and flags ∈ {MAP_PRIVATE | MAP_ANONYMOUS (0x22), MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK (0x20022), MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED (0x32)} (MAP_STACK is an advisory VM_NOHUGEPAGE no-op on Linux AArch64; MAP_FIXED | MAP_STACK = 0x20032 remains fail-closed). Linux ignores fd for anonymous mappings, and so does the model. With size := pageAlignUp len and newPerm := Perm. when prot = 0, Perm. when prot = 3:
-
[INTENTIONAL_SCOPE]: otherprot/flags/offset combinations (file mappings,MAP_SHARED,PROT_READalone,PROT_EXEC, a non-zero offset, ...) are unmodeled and raise .Exceptionrather than being silently approximated; -
len = 0returns-EINVALwithout consulting the oracle; -
without
MAP_FIXED, Linux treatsaddras a hint only; the model ignores it and sanitizescand := s.(oracle. mmap s. oracleIdx [INTENTIONAL_OVERAPPROX]) viasanitizeMmapAddrs.mem cand size (mm/mmap.c get_unmapped_area); when no free gap ofsizebytes exists (none), it transitions to the terminalExecStatus.outcome (s.OutOfMemory setStatus .OutOfMemory); -
with
MAP_FIXED(used forPROT_NONEguard pages andPROT_READ | PROT_WRITEdemand-zero page overlays),addrmust be page-aligned within [MMAP_MIN_ADDR, 2 ^ 48 - size] (mm/mmap.c get_unmapped_area->security_mmap_addr) and touch no frozen bytes. Withreclaimed := s.,mem. countRw addr size prot = PROT_NONEreplaces[addr, addr + size)with zero-filledPerm.pages and refundsprotNone reclaimedtos.;physMemAvail prot = PROT_READ | PROT_WRITEtransitions toExecStatus.whenOutOfMemory size > s., and otherwise replacesphysMemAvail + reclaimed [addr, addr + size)with zero-filledPerm.pages and setsrw s..physMemAvail := s. physMemAvail + reclaimed - size -
Physical memory accounting (
s.):physMemAvail PROT_NONEmappings consume0physical bytes (accountable_mappinginmm/);mmap.c PROT_READ | PROT_WRITEmappings deduct the net newly committed writable bytes (sizefor non-fixed mappings,size - reclaimednet forMAP_FIXED) froms., transitioning tophysMemAvail ExecStatus.if insufficient.OutOfMemory
def execMmap (s : State) : State :=
let len := (s.readReg64 Reg.x1).toNat
let prot := s.readReg64 Reg.x2
let flags := s.readReg64 Reg.x3
if (prot != MMAP_PROT_RW && prot != MMAP_PROT_NONE) ||
(flags != MMAP_FLAGS_PRIVATE_ANON && flags != MMAP_FLAGS_PRIVATE_ANON_STACK &&
flags != MMAP_FLAGS_PRIVATE_ANON_FIXED) ||
s.readReg64 Reg.x5 != 0#64 then
s.setStatus (.Exception "unmodeled mmap (only anonymous private mappings, PROT_NONE or PROT_READ|PROT_WRITE)")
else if len == 0 then
(s.writeReg64 Reg.x0 (-EINVAL)).advancePC
else if flags == MMAP_FLAGS_PRIVATE_ANON_FIXED then
let addr := s.readReg64 Reg.x0
let size := pageAlignUp len
if addr.toNat % PAGE_SIZE != 0 || addr.toNat < MMAP_MIN_ADDR ||
!decide (addr.toNat + size ≤ 2 ^ 48) then
s.setStatus (.Exception "unmodeled MAP_FIXED address")
else if !s.mem.allPerm (fun p => !p.frozen) addr size then
s.setStatus (.Exception "MAP_FIXED on frozen memory")
else
let reclaimed := s.mem.countRw addr size
if prot == MMAP_PROT_NONE then
(((s.setMem (((s.mem.unmapNonFrozen addr size).mapZero addr size).setPermNonFrozen addr size
Perm.protNone)).setPhysMemAvail (s.physMemAvail + reclaimed)).writeReg64 Reg.x0 addr).advancePC
else if !decide (size ≤ s.physMemAvail + reclaimed) then
s.setStatus .OutOfMemory
else
(((s.setMem ((s.mem.unmapNonFrozen addr size).mapZero addr size)).setPhysMemAvail
(s.physMemAvail + reclaimed - size)).writeReg64 Reg.x0 addr).advancePC
else
let size := pageAlignUp len
match sanitizeMmapAddr s.mem (s.oracle.mmap s.oracleIdx) size with
| some base =>
if prot == MMAP_PROT_NONE then
(((s.setMem ((s.mem.mapZero base size).setPermNonFrozen base size Perm.protNone)).nextOracle).writeReg64
Reg.x0 base).advancePC
else if !decide (size ≤ s.physMemAvail) then
s.setStatus .OutOfMemory
else
((((s.setMem (s.mem.mapZero base size)).setPhysMemAvail
(s.physMemAvail - size)).nextOracle).writeReg64 Reg.x0 base).advancePC
| none =>
s.setStatus .OutOfMemorydef execMunmap
munmap(addr, len) (__NR_munmap = 215, mm/ do_vmi_munmap). With size := pageAlignUp len:
-
if
addris not page-aligned (addr.toNat %PAGE_SIZE!= 0),len == 0, or[addr, addr + size)exceeds the 48-bit user virtual address space (addr.), returnstoNat + size > 2 ^ 48 -EINVAL; -
[INTENTIONAL_SCOPE]: if[addr, addr + size)intersects any frozen byte (somePerm.or somero Perm.), fails closed with .rx Exception"munmap on frozen memory"; -
otherwise unmaps all non-frozen bytes in
[addr, addr + size)(s., allowing already-unmapped holes in the range per POSIX/Linux), refundsmem. unmapNonFrozen addr size s.tomem. countRw addr size s., and returnsphysMemAvail 0.
def execMunmap (s : State) : State :=
let addr := s.readReg64 Reg.x0
let len := (s.readReg64 Reg.x1).toNat
let size := pageAlignUp len
if addr.toNat % PAGE_SIZE != 0 || len == 0 || !decide (addr.toNat + size ≤ 2 ^ 48) then
(s.writeReg64 Reg.x0 (-EINVAL)).advancePC
else if !s.mem.allPerm (fun p => !p.frozen) addr size then
s.setStatus (.Exception "munmap on frozen memory")
else
let reclaimed := s.mem.countRw addr size
(((s.setMem (s.mem.unmapNonFrozen addr size)).setPhysMemAvail
(s.physMemAvail + reclaimed)).writeReg64 Reg.x0 0#64).advancePCdef BRK_GUARD_GAP
256-page (1 MiB = 1,048,576 B) virtual-address guard gap above newPage enforced by execBrk (stack_guard_gap = 256UL << PAGE_SHIFT in mm/ vm_start_gap(next)), subsuming both the 1-page find_vma_intersection(mm, oldbrk, newbrk + PAGE_SIZE) guard against ordinary VMAs and the 256-page VM_GROWSDOWN guard below the stack VMA.
def BRK_GUARD_GAP : Nat := 256 * PAGE_SIZEdef execBrk
brk(addr) (__NR_brk = 214, mm/ SYSCALL_DEFINE1(brk) -> do_brk_flags / do_brk_munmap): query or adjust the program break without consulting Oracle.
-
[INTENTIONAL_SCOPE]: models single-threaded user-space execution underRLIMIT_DATA = RLIM_INFINITYandRLIMIT_AS = RLIM_INFINITY(check_data_rlimitinmm/,mmap.c:134 may_expand_vminmm/),vma.c:3028 mm->map_count ≤ sysctl_max_map_count(mm/), and no activevma.c:3031 MCL_FUTURE(mlock_future_okinmm/).vma.c:3034 -
Query or below-base (
addr = 0oraddr < s.): returns the current breakbrkStart s.with memory andbrk s.unchanged.brk -
Shrink or same-page (
newPage ≤ oldPage, where oldPage :=pageAlignUps.brk. and newPage :=toNat pageAlignUpaddr.toNat ,shrinkBytes := oldPage - newPage):-
if
newPage < oldPageand[newPage, oldPage)is not fully mapped (!s.mem. (·.mapped) (allPerm BitVec.64 newPage) shrinkBytes, e.g. afterofNat munmapof the heap tail, matching!brkvma || brkvma->vm_start >= oldbrkinmm/), returns the unchangedmmap.c:167 s.(brk origbrk) with memory andphysMemAvailunchanged; -
otherwise unmaps freed writable pages
[newPage, oldPage)(s.,mem. unmapWritable do_brk_munmapinmm/), refundsmmap.c:155 s.tomem. countRw newPage shrinkBytes s., setsphysMemAvail s., and returnsbrk := addr addr.
-
-
Grow (
oldPage < newPage,growBytes := newPage - oldPage):-
VMA collision +
BRK_GUARD_GAP(256 *PAGE_SIZE= 1 MiB) guard gap (newbrk +PAGE_SIZE> vm_start_gap(next) inmm/): if newPage +mmap.c:186 BRK_GUARD_GAP>USER_VA_LIMITor !s.mem. (canMap BitVec.64 oldPage) (growBytes +ofNat BRK_GUARD_GAP), returns the unchangeds.inbrk X0with status = .Runningso allocators (mallocng) deterministically fall back toSYS_mmap. -
Physical memory budget: if
s., transitions tophysMemAvail < growBytes ExecStatus..OutOfMemory -
Otherwise maps
[oldPage, newPage)read-write and zero-filled (s.), deductsmem. mapZero growBytesfroms., setsphysMemAvail s., and returnsbrk := addr addr.
-
def execBrk (s : State) : State :=
let addr := s.readReg64 Reg.x0
if addr == 0#64 || addr.ult s.brkStart then
(s.writeReg64 Reg.x0 s.brk).advancePC
else
let oldPage := pageAlignUp s.brk.toNat
let newPage := pageAlignUp addr.toNat
if newPage ≤ oldPage then
let shrinkBytes := oldPage - newPage
if !s.mem.allPerm (·.mapped) (BitVec.ofNat 64 newPage) shrinkBytes then
(s.writeReg64 Reg.x0 s.brk).advancePC
else
let reclaimed := s.mem.countRw (BitVec.ofNat 64 newPage) shrinkBytes
let mem := s.mem.unmapWritable (BitVec.ofNat 64 newPage) shrinkBytes
((((s.setBrk addr).setMem mem).setPhysMemAvail (s.physMemAvail + reclaimed)).writeReg64
Reg.x0 addr).advancePC
else
let growBytes := newPage - oldPage
if !decide (newPage + BRK_GUARD_GAP ≤ USER_VA_LIMIT) ||
!s.mem.canMap (BitVec.ofNat 64 oldPage) (growBytes + BRK_GUARD_GAP) then
(s.writeReg64 Reg.x0 s.brk).advancePC
else if !decide (growBytes ≤ s.physMemAvail) then
s.setStatus .OutOfMemory
else
let mem := s.mem.mapZero (BitVec.ofNat 64 oldPage) growBytes
((((s.setBrk addr).setMem mem).setPhysMemAvail (s.physMemAvail - growBytes)).writeReg64
Reg.x0 addr).advancePCend Arm64