~james/scope

scope/lib/paging.ml -rw-r--r-- 7.1 KiB
6e057d53 — james chore(import): snapshot james.dad.town/scope at 034e6b67d44c 14 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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