~james/scope

ref: cb7f12d3e8eeba75b4f02821fbb85d2741a69beb scope/lib/native.c -rw-r--r-- 8.9 KiB
cb7f12d3 — james ci(build): check ocaml versions and xen guest validation 13 days 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
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
263
264
265
266
267
268
269
270
271
272
273
#define _GNU_SOURCE
#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <caml/alloc.h>
#include <caml/custom.h>
#include <caml/fail.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#if defined(__has_include)
#if __has_include(<xenctrl.h>) && __has_include(<xenforeignmemory.h>)
#define SCOPE_XEN 1
#endif
#endif

#ifdef SCOPE_XEN
#include <dlfcn.h>
#include <errno.h>
#include <sys/mman.h>
#include <xenctrl.h>
#include <xenforeignmemory.h>
#include <xentoollog.h>
#include <xen/hvm/save.h>

static void quiet_message(xentoollog_logger *logger, xentoollog_level level,
                          int error, const char *context, const char *format, va_list arguments) {
  (void)logger; (void)level; (void)error; (void)context; (void)format; (void)arguments;
}

static void quiet_destroy(xentoollog_logger *logger) {
  (void)logger;
}

static xentoollog_logger quiet_logger = {
  .vmessage = quiet_message,
  .progress = NULL,
  .destroy = quiet_destroy
};

struct api {
  void *control;
  void *foreign;
  __typeof__(&xc_interface_open) control_open;
  __typeof__(&xc_interface_close) control_close;
  __typeof__(&xc_domain_getinfolist) info;
  __typeof__(&xc_domain_pause) pause;
  __typeof__(&xc_domain_unpause) unpause;
  __typeof__(&xc_domain_hvm_getcontext_partial) context;
  __typeof__(&xenforeignmemory_open) foreign_open;
  __typeof__(&xenforeignmemory_close) foreign_close;
  __typeof__(&xenforeignmemory_map) map;
  __typeof__(&xenforeignmemory_unmap) unmap;
};

static struct api api;

static bool load_api(void) {
  if (api.control && api.foreign) return true;
  api.control = dlopen("libxenctrl.so", RTLD_NOW | RTLD_LOCAL);
  api.foreign = dlopen("libxenforeignmemory.so", RTLD_NOW | RTLD_LOCAL);
  if (!api.control || !api.foreign) goto failed;
#define LOAD(member, library, symbol) \
  do { *(void **)(&api.member) = dlsym(api.library, symbol); if (!api.member) goto failed; } while (0)
  LOAD(control_open, control, "xc_interface_open");
  LOAD(control_close, control, "xc_interface_close");
  LOAD(info, control, "xc_domain_getinfolist");
  LOAD(pause, control, "xc_domain_pause");
  LOAD(unpause, control, "xc_domain_unpause");
  LOAD(context, control, "xc_domain_hvm_getcontext_partial");
  LOAD(foreign_open, foreign, "xenforeignmemory_open");
  LOAD(foreign_close, foreign, "xenforeignmemory_close");
  LOAD(map, foreign, "xenforeignmemory_map");
  LOAD(unmap, foreign, "xenforeignmemory_unmap");
#undef LOAD
  return true;
failed:
  if (api.control) dlclose(api.control);
  if (api.foreign) dlclose(api.foreign);
  memset(&api, 0, sizeof(api));
  return false;
}

struct session {
  xc_interface *control;
  xenforeignmemory_handle *foreign;
  uint32_t domid;
  uint16_t vcpu;
  bool owned_pause;
  void *mapping;
  xen_domain_handle_t identity;
};

static bool inspect(struct session *s, xc_domaininfo_t *info) {
  return api.info(s->control, s->domid, 1, info) == 1 && info->domain == s->domid &&
    memcmp(info->handle, s->identity, sizeof(s->identity)) == 0;
}

static int release(struct session *s) {
  int failed = 0;
  if (s->mapping) {
    if (api.unmap(s->foreign, s->mapping, 1) != 0) failed = 1;
    s->mapping = NULL;
  }
  if (s->owned_pause) {
    xc_domaininfo_t info;
    if (!inspect(s, &info) || api.unpause(s->control, s->domid) != 0) failed = 1;
    s->owned_pause = false;
  }
  if (s->foreign && api.foreign_close(s->foreign) != 0) failed = 1;
  if (s->control && api.control_close(s->control) != 0) failed = 1;
  s->foreign = NULL;
  s->control = NULL;
  return failed;
}

static void finalise(value block) {
  struct session *s = Data_custom_val(block);
  if (release(s)) fprintf(stderr, "scope: xen cleanup failed; inspect domain pause state\n");
}

static struct custom_operations operations = {
  .identifier = "scope.xen.session.v1",
  .finalize = finalise,
  .compare = custom_compare_default,
  .hash = custom_hash_default,
  .serialize = custom_serialize_default,
  .deserialize = custom_deserialize_default,
  .compare_ext = custom_compare_ext_default,
  .fixed_length = custom_fixed_length_default
};

static void check(struct session *s) {
  xc_domaininfo_t info;
  if (!s->control) caml_failwith("xen session is closed");
  if (!inspect(s, &info)) caml_failwith("domain identity changed or domain disappeared");
  if (!(info.flags & XEN_DOMINF_paused)) caml_failwith("domain is no longer paused");
}

CAMLprim value scope_xen_available(value unit) {
  CAMLparam1(unit);
  CAMLreturn(Val_bool(load_api()));
}

CAMLprim value scope_xen_open(value domain, value vcpu, value pause) {
  CAMLparam3(domain, vcpu, pause);
  CAMLlocal1(block);
  if (!load_api()) caml_failwith("cannot load xen control and foreign memory libraries");
  block = caml_alloc_custom(&operations, sizeof(struct session), 0, 1);
  struct session *s = Data_custom_val(block);
  memset(s, 0, sizeof(*s));
  s->domid = Int_val(domain);
  s->vcpu = Int_val(vcpu);
  s->control = api.control_open(&quiet_logger, &quiet_logger, 0);
  if (!s->control) caml_failwith("cannot open xen control interface");
  xc_domaininfo_t info;
  if (api.info(s->control, s->domid, 1, &info) != 1 || info.domain != s->domid ||
      !(info.flags & XEN_DOMINF_hvm_guest) || s->vcpu > info.max_vcpu_id) {
    release(s);
    caml_failwith("domain absent, not hvm, or vcpu outside domain range");
  }
  memcpy(s->identity, info.handle, sizeof(s->identity));
  if (Bool_val(pause)) {
    if (api.pause(s->control, s->domid) != 0) {
      release(s);
      caml_failwith("cannot acquire xen controller pause");
    }
    /* pause -> capture -> unpause, release only the reference acquired here */
    s->owned_pause = true;
  } else if (!(info.flags & XEN_DOMINF_paused)) {
    release(s);
    caml_failwith("domain must already be paused or capture must use --pause");
  }
  s->foreign = api.foreign_open(&quiet_logger, 0);
  if (!s->foreign) {
    if (release(s)) caml_failwith("foreign memory open and pause cleanup failed; inspect domain");
    caml_failwith("cannot open xen foreign memory interface");
  }
  CAMLreturn(block);
}

CAMLprim value scope_xen_registers(value block) {
  CAMLparam1(block);
  CAMLlocal4(array, number, identity, pair);
  struct session *s = Data_custom_val(block);
  check(s);
  struct hvm_hw_cpu cpu;
  if (api.context(s->control, s->domid, HVM_SAVE_CODE(CPU), s->vcpu, &cpu, sizeof(cpu)) != 0)
    caml_failwith("cannot read hvm vcpu context");
  uint64_t values[] = {cpu.cr0, cpu.cr2, cpu.cr3, cpu.cr4, cpu.msr_efer, cpu.rflags, cpu.rip};
  char encoded[sizeof(s->identity) * 2 + 1];
  for (size_t i = 0; i < sizeof(s->identity); i++) snprintf(encoded + i * 2, 3, "%02x", s->identity[i]);
  array = caml_alloc(7, 0);
  for (int i = 0; i < 7; i++) Store_field(array, i, Val_unit);
  for (int i = 0; i < 7; i++) {
    number = caml_copy_int64(values[i]);
    Store_field(array, i, number);
  }
  identity = caml_copy_string(encoded);
  pair = caml_alloc_tuple(2);
  Store_field(pair, 0, array);
  Store_field(pair, 1, identity);
  CAMLreturn(pair);
}

CAMLprim value scope_xen_read(value block, value physical) {
  CAMLparam2(block, physical);
  CAMLlocal1(bytes);
  struct session *s = Data_custom_val(block);
  check(s);
  if (s->mapping) caml_failwith("previous foreign unmap failed");
  uint64_t address = Int64_val(physical);
  if (address & 4095) caml_invalid_argument("unaligned guest physical frame");
  xen_pfn_t frame = address >> 12;
  int error = 0;
  void *mapping = api.map(s->foreign, s->domid, PROT_READ, 1, &frame, &error);
  if (mapping && mapping != MAP_FAILED) s->mapping = mapping;
  if (!mapping || mapping == MAP_FAILED || error) {
    int saved_error = error ? error : errno;
    if (s->mapping && api.unmap(s->foreign, mapping, 1) == 0) s->mapping = NULL;
    char reason[160];
    snprintf(reason, sizeof(reason), "foreign map failed at 0x%016llx: errno=%d",
      (unsigned long long)address, saved_error);
    caml_failwith(reason);
  }
  char copy[4096];
  memcpy(copy, mapping, sizeof(copy));
  if (api.unmap(s->foreign, mapping, 1) != 0) caml_failwith("foreign unmap failed");
  s->mapping = NULL;
  bytes = caml_alloc_initialized_string(sizeof(copy), copy);
  CAMLreturn(bytes);
}

CAMLprim value scope_xen_close(value block) {
  CAMLparam1(block);
  if (release(Data_custom_val(block))) caml_failwith("xen cleanup failed; inspect domain pause state");
  CAMLreturn(Val_unit);
}

#else
CAMLprim value scope_xen_available(value unit) {
  (void)unit;
  return Val_false;
}

static void unavailable(void) {
  caml_failwith("xen capture unavailable; install xen development headers and rebuild scope");
}

CAMLprim value scope_xen_open(value domain, value vcpu, value pause) {
  (void)domain; (void)vcpu; (void)pause;
  unavailable();
  return Val_unit;
}

CAMLprim value scope_xen_registers(value block) {
  (void)block;
  unavailable();
  return Val_unit;
}

CAMLprim value scope_xen_read(value block, value address) {
  (void)block; (void)address;
  unavailable();
  return Val_unit;
}

CAMLprim value scope_xen_close(value block) {
  (void)block;
  return Val_unit;
}
#endif