package mirage-qubes

  1. Overview
  2. Docs

Source file gUI.ml

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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
(* Copyright (C) 2015, Thomas Leonard
   See the README file for details. *)

open Lwt.Infix
open Formats.GUI

module QV = Msg_chan.Make(Framing)

let src = Logs.Src.create "qubes.gui" ~doc:"Qubes GUId agent"
module Log = (val Logs.src_log src : Logs.LOG)

(* QUBES_GUID_PROTOCOL_VERSION_MAJOR << 16 | QUBES_GUID_PROTOCOL_VERSION_MINOR
   see ./qubes-gui-common/include/qubes-gui-protocol.h *)
let qubes_gui_protocol_version_linux = Int32.logor 0x1_0000_l 0x0001_l

let gui_agent_port =
  match Vchan.Port.of_string "6000" with
  | Error (`Msg msg) -> failwith msg
  | Ok port -> port

type event =
  | UNIT of unit (* placeholder for unimplemented events *)
  | Keypress of msg_keypress_t
  | Focus of msg_focus_t
  | Motion of msg_motion_t
  | Clipboard_request
  | Clipboard_data of Cstruct.t
  | Configure of Formats.GUI.msg_configure_t
  | Window_crossing of msg_crossing_t
  | Window_destroy
  | Window_close
  | Button of msg_button_t

let pp_event fmt event =
  let pf() = Format.fprintf fmt in
  match event with
  | UNIT () -> pf() "UNIT"
  | Button _ -> pf() "Button"
  | Clipboard_request -> pf() "Clipboard_request"
  | Clipboard_data cs -> pf() "Clipboard_data: %S" (Cstruct.to_string cs)
  | Configure x -> pf() "Configure: @[x=%ld;@ y=%ld;@ width=%ld;@ height=%ld@]"
                     x.x x.y x.width x.height
  | Focus {mode;detail} -> pf() "Focus mode: %ld detail: %ld" mode detail
  | Keypress {x;y;state;keycode; ty = _ } ->
    pf() "Keypress x: %ld y: %ld state: %ld keycode: %ld" x y state keycode
  | Motion m -> pf() "Motion x: %d y: %d state: %ld is_hint: %d"
                      m.x m.y m.state m.is_hint
  | Window_close -> pf() "Window_close"
  | Window_crossing {ty;x;y ; state ; mode ; detail ; focus } ->
    pf() "Window_crossing type type: %ld x: %ld y: %ld \
          state: %ld mode: %ld detail: %ld focus: %ld "
      ty x y state mode detail focus
  | Window_destroy -> pf() "Window_destroy"

type window_id = Cstruct.uint32
type window = {no : window_id ; mvar : event Lwt_mvar.t ; qv : QV.t }
type t = { qv : QV.t ;
           mutable mvar : window list}

let decode_KEYPRESS buf =
    let keypress : Formats.GUI.msg_keypress_t = {
      x = get_msg_keypress_x buf;
      y = get_msg_keypress_y buf;
      state = get_msg_keypress_state buf;
      keycode = get_msg_keypress_keycode buf;
      ty = get_msg_keypress_ty buf;
    } in
    Keypress keypress

let decode_FOCUS buf =
  let focus : Formats.GUI.msg_focus_t = {
    mode = get_msg_focus_mode buf;
    detail = get_msg_focus_detail buf;
  } in
  Focus focus

let decode_MSG_CLOSE buf =
  Log.warn (fun f -> f "Event: CLOSE: %a" Cstruct.hexdump_pp buf) ;
  Window_close

let decode_CLIPBOARD_DATA buf =
  Log.warn (fun f -> f "Event: CLIPBOARD_DATA: %a" Cstruct.hexdump_pp buf);
  let len = get_msg_clipboard_data_len buf |> Int32.to_int in
  match
    Int32.compare (get_msg_clipboard_data_len buf) 0l = -1
    || Cstruct.length buf + sizeof_msg_clipboard_data <> len with
  | true ->
    Logs.warn (fun m -> m "Got invalid CLIPBOARD_DATA msg from dom0");
    UNIT ()
  | false ->
    (* TODO expose the window id of the recipient window *)
    Clipboard_data (Cstruct.sub buf sizeof_msg_clipboard_data len)

let int32_of_window (w : window) : int32 = w.no

let decode_MSG_MOTION buf =
  match Formats.GUI.decode_msg_motion buf with
  | Some m ->
    Log.warn (fun f -> f "Motion event: x: %d y: %d state: %ld is_hint: %d"
                 m.x m.y m.state m.is_hint);
    Motion m
  | None ->
    Log.warn (fun f -> f "attempted to decode a motion event, but we were not successful: %a" Cstruct.hexdump_pp buf);
    UNIT ()

let decode_CONFIGURE buf =
  match decode_msg_configure buf with
  | Some m -> Configure m
  | None ->
    Log.warn (fun f -> f "failed decoding CONFIGURE message from dom0: %a"
                 Cstruct.hexdump_pp buf) ;
    UNIT ()

let recv_event (window:window) =
  Lwt_mvar.take window.mvar

let debug_window w =
  let rec loop () = recv_event w >>= fun e ->
    Log.info (fun m -> m "debug_window [%ld]: %a" w.no pp_event e);
    loop ()
  in loop

let send t cs_lst = QV.send t.qv cs_lst

let set_title (window : window) title =
  QV.send window.qv
  [Formats.GUI.make_msg_wmname ~window:window.no ~wmname:title]

let create_window ?(parent=(0l:window_id)) ~x ~y ~title ~width ~height t
  : window S.or_eof Lwt.t =
  let w : window = { no = List.length t.mvar |> Int32.of_int ;
                     mvar = Lwt_mvar.create_empty () ;
                     qv = t.qv }
  in
  let window = w.no in
  Logs.warn (fun m -> m "Qubes.GUI: Creating new window id %ld" window);
  t.mvar <- w :: t.mvar ;
  let messages =
    let override_redirect = 0l in
    [Formats.GUI.make_msg_create ~width ~height ~x ~y
       ~override_redirect ~parent ~window ;
     Formats.GUI.make_msg_map_info ~override_redirect ~transient_for:0l ~window;
     Formats.GUI.make_msg_wmname ~window ~wmname:title ;
     Formats.GUI.make_msg_configure ~width ~height ~x ~y ~window ;
    ]
  in
  send t messages
  >>= function | `Ok () -> Lwt.return (`Ok w)
               | `Eof -> Lwt.return `Eof

let connect ~domid () =
  Log.info (fun f -> f "waiting for client...");
  QV.server ~domid ~port:gui_agent_port () >>= fun qv ->
  (* qubesgui_init_connection *)
  let version = Cstruct.create sizeof_gui_protocol_version in
  set_gui_protocol_version_version version qubes_gui_protocol_version_linux;
  QV.send qv [version] >>= function
  | `Eof -> Lwt.fail_with "End-of-file sending protocol version"
  | `Ok () ->
  QV.recv_fixed qv sizeof_xconf >>= function
  | `Eof -> Lwt.fail_with "End-of-file getting X configuration"
  | `Ok conf ->
  let screen_w = get_xconf_w conf in
  let screen_h = get_xconf_h conf in
  let xdepth = get_xconf_depth conf in
  let xmem = get_xconf_mem conf in
  Log.info (fun f ->
      f "client connected (screen size: %ldx%ld depth: %ld mem: %ldx)"
        screen_w screen_h xdepth xmem);
  let main_window = {no = 0l ; qv ; mvar = Lwt_mvar.create_empty ()} in
  Lwt.async (debug_window main_window) ;
  Lwt.return { qv ;
               mvar = [main_window] }

let rec listen t () =
  QV.recv t.qv >>= function
  | `Eof -> failwith "End-of-file from GUId in dom0"
  | `Ok (msg_header , msg_buf) ->
  let window = get_msg_header_window msg_header in
  let send_to_window event =
    match List.find (fun t -> t.no = window) t.mvar with
    | w -> Lwt_mvar.put w.mvar event
    | exception _ -> Log.warn (fun m -> m "No such window %ld" window);
                     Lwt.return_unit
  in
  let msg_len = get_msg_header_untrusted_len msg_header |> Int32.to_int in
  send_to_window
  begin match int_to_msg_type (get_msg_header_ty msg_header) with

  (* handle fixed-length messages *)

  | Some ( MSG_KEYPRESS | MSG_BUTTON | MSG_MOTION | MSG_CROSSING | MSG_FOCUS
         | MSG_CREATE | MSG_DESTROY | MSG_CONFIGURE | MSG_SHMIMAGE
         | MSG_EXECUTE | MSG_WMNAME | MSG_KEYMAP_NOTIFY | MSG_WINDOW_HINTS
         | MSG_WINDOW_FLAGS | MSG_WMCLASS | MSG_CLIPBOARD_REQ
         | MSG_CLOSE as msg)
    when (match msg_type_size msg with Some x -> x <> msg_len | None -> true) ->
    Log.warn (fun f -> f "BUG: expected_size [%d] <> msg_len [%d] for fixed-\
                          size msg! msg_header: %a@ Received raw buffer:: %a"
                 (match msg_type_size msg with Some x -> x | None -> -1)
                 msg_len
                 Cstruct.hexdump_pp msg_header
                 Cstruct.hexdump_pp msg_buf) ;
    UNIT()
  | Some MSG_MAP ->
    Log.warn (fun f -> f "Event: MAP: %a" Cstruct.hexdump_pp msg_buf) ;
    UNIT()
  | Some MSG_KEYPRESS -> decode_KEYPRESS msg_buf
  | Some MSG_FOCUS -> decode_FOCUS msg_buf
  | Some MSG_MOTION -> decode_MSG_MOTION msg_buf
  | Some MSG_CLIPBOARD_REQ ->
    Log.warn (fun f -> f "Event: dom0 requested our clipboard.") ;
    Clipboard_request
  | Some MSG_CROSSING -> begin match decode_msg_crossing msg_buf with
      | Some event -> Window_crossing event
      | None -> Log.warn (fun m -> m "Invalid MSG_CROSSING during decoding %a"
                             Cstruct.hexdump_pp msg_buf)
              ; UNIT ()
      end
  | Some MSG_CLOSE -> decode_MSG_CLOSE msg_buf
  | Some MSG_BUTTON -> begin match decode_msg_button msg_buf with
      | Some button_event -> Button button_event
      | None -> Log.warn (fun m -> m "Invalid MSG_BUTTON decoding %a"
                             Cstruct.hexdump_pp msg_buf)
        ; UNIT ()
      end
  | Some MSG_KEYMAP_NOTIFY ->
    (* Synchronize the keyboard state (key pressed/released) with dom0 *)
    Log.warn (fun f -> f "Event: KEYMAP_NOTIFY: %S"
      Cstruct.(to_string msg_buf)) ;
    UNIT()
  | Some MSG_WINDOW_FLAGS ->
    Log.warn (fun f -> f "Event: WINDOW_FLAGS: %S" Cstruct.(to_string msg_buf))
      ; UNIT ()
  | Some MSG_CONFIGURE ->
    Log.warn (fun f -> f "Event: CONFIGURE (should reply with this): %a"
                 Cstruct.hexdump_pp msg_buf) ;
    (* TODO here we should ACK to Qubes that we accept the new dimensions,
            atm this is the responsibility of the user: *)
    decode_CONFIGURE msg_buf

  (* parse variable-length messages: *)

  | Some MSG_CLIPBOARD_DATA -> decode_CLIPBOARD_DATA msg_buf

  (* handle unimplemented/unexpected messages:*)

  | Some ( MSG_UNMAP | MSG_MFNDUMP | MSG_DOCK | MSG_WINDOW_HINTS
         | MSG_SHMIMAGE | MSG_WMCLASS | MSG_EXECUTE | MSG_CREATE
         | MSG_WMNAME | MSG_DESTROY ) ->
    (* Handle messages that are appvm->dom0 and thus dom0 is not supposed
       to send to the VM: *)
    Log.warn (fun f ->
        f "UNEXPECTED message received. Data: %a"
          Cstruct.hexdump_pp msg_buf); UNIT()
  | None ->
    Log.warn (fun f -> f "Unexpected data with unknown type: [%a] %aa"
                 Cstruct.hexdump_pp msg_header
                 Cstruct.hexdump_pp msg_buf) ;
    UNIT()
  end
  >>= fun () -> listen t ()
OCaml

Innovation. Community. Security.