open Model type entry = Absent | Invalid of string | Table of Address.Physical.t | Leaf of int * Address.Physical.t let shift level = 12 + (9 * (level - 1)) let span level = Int64.shift_left 1L (shift level) let decode features ~nxe ~level raw = let open Address in if not (bit raw 0) then Absent else if bit raw 63 && not nxe then Invalid "nx bit with nxe disabled" else if Int64.logand raw (Int64.logand (mask 52) (Int64.lognot (mask features.phys_bits))) <> 0L then Invalid "physical address uses reserved high bits" else if level = 4 && bit raw 7 then Invalid "page size bit in pml4 entry" else if level = 3 && bit raw 7 && not features.gb_pages then Invalid "1 gib pages are not exposed to the guest" else let address = Int64.logand raw (Int64.logand (mask features.phys_bits) (Int64.lognot 0xfffL)) in if level = 1 then Leaf (12, Physical.of_int64 address) else if bit raw 7 then let bits = shift level in (* huge leaf: [ frame | reserved | pat | flags ], bit 12 stays out of the frame *) if Int64.logand raw (Int64.logand (mask bits) (Int64.lognot 0x1fffL)) <> 0L then Invalid "misaligned huge page uses reserved address bits" else Leaf (bits, Physical.of_int64 (align address bits)) else Table (Physical.of_int64 address) let error_code context request ~present ~reserved = (if present then 1 else 0) lor (if request.access = Write then 2 else 0) lor (if request.privilege = User then 4 else 0) lor (if reserved then 8 else 0) lor (if request.access = Execute && (Address.bit context.registers.efer 11 || Address.bit context.registers.cr4 20) then 16 else 0) let walk context read address = validate context; let virtual_raw = Address.Virtual.to_int64 address in if not (Address.canonical virtual_raw) then { trace = []; outcome = Fault { vector = 13; error_code = 0; reason = "noncanonical linear address"; level = None } } else let rec go level table permissions trace = match read table with | Error reason -> { trace = List.rev trace; outcome = Incomplete reason } | Ok page when String.length page <> 4096 -> { trace = List.rev trace; outcome = Incomplete "table read is not 4096 bytes" } | Ok page -> let index = Int64.(to_int (logand (shift_right_logical virtual_raw (shift level)) 511L)) in let raw = String.get_int64_le page (index * 8) in let effective = narrow permissions raw in let step = { level; table; index; raw; effective } in let trace = step :: trace in match decode context.features ~nxe:(Address.bit context.registers.efer 11) ~level raw with | Absent -> { trace = List.rev trace; outcome = Fault { vector = 14; error_code = 0; reason = "entry not present"; level = Some level } } | Invalid reason -> { trace = List.rev trace; outcome = Fault { vector = 14; error_code = 9; reason; level = Some level } } | Table next -> go (level - 1) next effective trace | Leaf (bits, base) -> let trace = List.rev trace in let physical_address = Address.Physical.of_int64 Int64.(add (Address.Physical.to_int64 base) (logand virtual_raw (Address.mask bits))) in { trace; outcome = Mapped { virtual_address = address; physical_address; size = Int64.shift_left 1L bits; permissions = effective; leaf = raw; trace } } in go 4 (root context) unrestricted [] let explain context read address request = let result = walk context read address in let fault reason level = { result with outcome = Fault { vector = 14; error_code = error_code context request ~present:true ~reserved:false; reason; level } } in match result.outcome with | Incomplete _ -> result | Fault f when f.vector = 13 -> result | Fault f -> { result with outcome = Fault { f with error_code = error_code context request ~present:(f.error_code land 1 <> 0) ~reserved:(f.error_code land 8 <> 0) } } | Mapped mapping -> let p = mapping.permissions and r = context.registers in let first predicate = List.find_opt predicate result.trace |> Option.map (fun (step : step) -> step.level) in if request.privilege = User && not p.user then fault "supervisor mapping denies user access" (first (fun s -> not (Address.bit s.raw 2))) else if request.access = Write && not p.writable && (request.privilege = User || Address.bit r.cr0 16) then fault "read-only entry denies write" (first (fun s -> not (Address.bit s.raw 1))) else if request.access = Execute && not p.executable then fault "nx entry denies instruction fetch" (first (fun s -> Address.bit s.raw 63)) else if request.privilege = Supervisor && p.user && request.access = Execute && Address.bit r.cr4 20 then fault "smep denies supervisor fetch from user mapping" None else if request.privilege = Supervisor && p.user && request.access <> Execute && Address.bit r.cr4 21 && not request.ac then fault "smap denies explicit supervisor data access with ac clear" None else result type state = Present of mapping | Unknown of string | Broken of string type region = { start : int64; length : int64; state : state } let last region = Int64.pred (Int64.add region.start region.length) let enumerate ?(max_entries = 2_000_000) context read = validate context; let count = ref 0 and acc = ref [] in let emit start length state = acc := { start; length; state } :: !acc in let canonicalise x = if Address.bit x 47 then Int64.logor x 0xffff000000000000L else x in let rec go level table prefix permissions trace = match read table with | Error reason -> if level = 4 then begin emit 0L 0x800000000000L (Unknown reason); emit 0xffff800000000000L 0x800000000000L (Unknown reason) end else emit (canonicalise prefix) (Int64.mul 512L (span level)) (Unknown reason) | Ok page -> if String.length page <> 4096 then invalid_arg "table read is not 4096 bytes"; for index = 0 to 511 do incr count; if !count > max_entries then raise (Limit "page-table traversal budget exceeded"); let raw = String.get_int64_le page (index * 8) in let prefix = Int64.logor prefix (Int64.shift_left (Int64.of_int index) (shift level)) in let start = canonicalise prefix in let effective = narrow permissions raw in let trace = { level; table; index; raw; effective } :: trace in match decode context.features ~nxe:(Address.bit context.registers.efer 11) ~level raw with | Absent -> () | Invalid reason -> emit start (span level) (Broken reason) | Table next -> go (level - 1) next prefix effective trace | Leaf (bits, physical_address) -> let trace = List.rev trace in let mapping = { virtual_address = Address.Virtual.of_int64 start; physical_address; size = Int64.shift_left 1L bits; permissions = effective; leaf = raw; trace } in emit start mapping.size (Present mapping) done in go 4 (root context) 0L unrestricted []; List.rev !acc