1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5Libxc Migration v2 streams
6
7Record structures as per docs/specs/libxc-migration-stream.pandoc, and
8verification routines.
9"""
10
11import sys
12
13from struct import calcsize, unpack
14
15from xen.migration.verify import StreamError, RecordError, VerifyBase
16
17# Image Header
18IHDR_FORMAT = "!QIIHHI"
19
20IHDR_MARKER  = 0xffffffffffffffff
21IHDR_IDENT   = 0x58454E46 # "XENF" in ASCII
22
23IHDR_OPT_BIT_ENDIAN = 0
24IHDR_OPT_LE = (0 << IHDR_OPT_BIT_ENDIAN)
25IHDR_OPT_BE = (1 << IHDR_OPT_BIT_ENDIAN)
26
27IHDR_OPT_RESZ_MASK = 0xfffe
28
29# Domain Header
30DHDR_FORMAT = "IHHII"
31
32DHDR_TYPE_x86_pv  = 0x00000001
33DHDR_TYPE_x86_hvm = 0x00000002
34
35dhdr_type_to_str = {
36    DHDR_TYPE_x86_pv  : "x86 PV",
37    DHDR_TYPE_x86_hvm : "x86 HVM",
38}
39
40# Records
41RH_FORMAT = "II"
42
43REC_TYPE_end                        = 0x00000000
44REC_TYPE_page_data                  = 0x00000001
45REC_TYPE_x86_pv_info                = 0x00000002
46REC_TYPE_x86_pv_p2m_frames          = 0x00000003
47REC_TYPE_x86_pv_vcpu_basic          = 0x00000004
48REC_TYPE_x86_pv_vcpu_extended       = 0x00000005
49REC_TYPE_x86_pv_vcpu_xsave          = 0x00000006
50REC_TYPE_shared_info                = 0x00000007
51REC_TYPE_tsc_info                   = 0x00000008
52REC_TYPE_hvm_context                = 0x00000009
53REC_TYPE_hvm_params                 = 0x0000000a
54REC_TYPE_toolstack                  = 0x0000000b
55REC_TYPE_x86_pv_vcpu_msrs           = 0x0000000c
56REC_TYPE_verify                     = 0x0000000d
57REC_TYPE_checkpoint                 = 0x0000000e
58REC_TYPE_checkpoint_dirty_pfn_list  = 0x0000000f
59REC_TYPE_static_data_end            = 0x00000010
60REC_TYPE_x86_cpuid_policy           = 0x00000011
61REC_TYPE_x86_msr_policy             = 0x00000012
62
63rec_type_to_str = {
64    REC_TYPE_end                        : "End",
65    REC_TYPE_page_data                  : "Page data",
66    REC_TYPE_x86_pv_info                : "x86 PV info",
67    REC_TYPE_x86_pv_p2m_frames          : "x86 PV P2M frames",
68    REC_TYPE_x86_pv_vcpu_basic          : "x86 PV vcpu basic",
69    REC_TYPE_x86_pv_vcpu_extended       : "x86 PV vcpu extended",
70    REC_TYPE_x86_pv_vcpu_xsave          : "x86 PV vcpu xsave",
71    REC_TYPE_shared_info                : "Shared info",
72    REC_TYPE_tsc_info                   : "TSC info",
73    REC_TYPE_hvm_context                : "HVM context",
74    REC_TYPE_hvm_params                 : "HVM params",
75    REC_TYPE_toolstack                  : "Toolstack",
76    REC_TYPE_x86_pv_vcpu_msrs           : "x86 PV vcpu msrs",
77    REC_TYPE_verify                     : "Verify",
78    REC_TYPE_checkpoint                 : "Checkpoint",
79    REC_TYPE_checkpoint_dirty_pfn_list  : "Checkpoint dirty pfn list",
80    REC_TYPE_static_data_end            : "Static data end",
81    REC_TYPE_x86_cpuid_policy           : "x86 CPUID policy",
82    REC_TYPE_x86_msr_policy             : "x86 MSR policy",
83}
84
85# page_data
86PAGE_DATA_FORMAT             = "II"
87PAGE_DATA_PFN_MASK           = (1 << 52) - 1
88PAGE_DATA_PFN_RESZ_MASK      = ((1 << 60) - 1) & ~((1 << 52) - 1)
89
90# flags from xen/public/domctl.h: XEN_DOMCTL_PFINFO_* shifted by 32 bits
91PAGE_DATA_TYPE_SHIFT         = 60
92PAGE_DATA_TYPE_LTABTYPE_MASK = (0x7 << PAGE_DATA_TYPE_SHIFT)
93PAGE_DATA_TYPE_LTAB_MASK     = (0xf << PAGE_DATA_TYPE_SHIFT)
94PAGE_DATA_TYPE_LPINTAB       = (0x8 << PAGE_DATA_TYPE_SHIFT) # Pinned pagetable
95
96PAGE_DATA_TYPE_NOTAB         = (0x0 << PAGE_DATA_TYPE_SHIFT) # Regular page
97PAGE_DATA_TYPE_L1TAB         = (0x1 << PAGE_DATA_TYPE_SHIFT) # L1 pagetable
98PAGE_DATA_TYPE_L2TAB         = (0x2 << PAGE_DATA_TYPE_SHIFT) # L2 pagetable
99PAGE_DATA_TYPE_L3TAB         = (0x3 << PAGE_DATA_TYPE_SHIFT) # L3 pagetable
100PAGE_DATA_TYPE_L4TAB         = (0x4 << PAGE_DATA_TYPE_SHIFT) # L4 pagetable
101PAGE_DATA_TYPE_BROKEN        = (0xd << PAGE_DATA_TYPE_SHIFT) # Broken
102PAGE_DATA_TYPE_XALLOC        = (0xe << PAGE_DATA_TYPE_SHIFT) # Allocate-only
103PAGE_DATA_TYPE_XTAB          = (0xf << PAGE_DATA_TYPE_SHIFT) # Invalid
104
105# x86_pv_info
106X86_PV_INFO_FORMAT        = "BBHI"
107
108X86_PV_P2M_FRAMES_FORMAT  = "II"
109
110# x86_pv_vcpu_{basic,extended,xsave,msrs}
111X86_PV_VCPU_HDR_FORMAT    = "II"
112
113# x86_tsc_info
114X86_TSC_INFO_FORMAT       = "IIQII"
115
116# hvm_params
117HVM_PARAMS_ENTRY_FORMAT   = "QQ"
118HVM_PARAMS_FORMAT         = "II"
119
120# x86_cpuid_policy => xen_cpuid_leaf_t[]
121X86_CPUID_POLICY_FORMAT   = "IIIIII"
122
123# x86_msr_policy => xen_msr_entry_t[]
124X86_MSR_POLICY_FORMAT     = "QII"
125
126class VerifyLibxc(VerifyBase):
127    """ Verify a Libxc v2 (or later) stream """
128
129    def __init__(self, info, read):
130        VerifyBase.__init__(self, info, read)
131
132        self.version = 0
133        self.squashed_pagedata_records = 0
134
135
136    def verify(self):
137        """ Verity a libxc stream """
138
139        self.verify_ihdr()
140        self.verify_dhdr()
141
142        while self.verify_record() != REC_TYPE_end:
143            pass
144
145
146    def verify_ihdr(self):
147        """ Verify an Image Header """
148        marker, ident, version, options, res1, res2 = \
149            self.unpack_exact(IHDR_FORMAT)
150
151        if marker != IHDR_MARKER:
152            raise StreamError("Bad image marker: Expected 0x%x, got 0x%x" %
153                              (IHDR_MARKER, marker))
154
155        if ident != IHDR_IDENT:
156            raise StreamError("Bad image id: Expected 0x%x, got 0x%x" %
157                              (IHDR_IDENT, ident))
158
159        if not (2 <= version <= 3):
160            raise StreamError(
161                "Unknown image version: Expected 2 <= ver <= 3, got %d" %
162                (version, ))
163
164        self.version = version
165
166        if options & IHDR_OPT_RESZ_MASK:
167            raise StreamError("Reserved bits set in image options field: 0x%x" %
168                              (options & IHDR_OPT_RESZ_MASK))
169
170        if res1 != 0 or res2 != 0:
171            raise StreamError(
172                "Reserved bits set in image header: 0x%04x:0x%08x" %
173                (res1, res2))
174
175        if ( (sys.byteorder == "little") and
176             ((options & IHDR_OPT_BIT_ENDIAN) != IHDR_OPT_LE) ):
177            raise StreamError(
178                "Stream is not native endianess - unable to validate")
179
180        endian = ["little", "big"][options & IHDR_OPT_LE]
181        self.info("Libxc Image Header: Version %d, %s endian" %
182                  (version, endian))
183
184
185    def verify_dhdr(self):
186        """ Verify a domain header """
187
188        gtype, page_shift, res1, major, minor = \
189            self.unpack_exact(DHDR_FORMAT)
190
191        if gtype not in dhdr_type_to_str:
192            raise StreamError("Unrecognised domain type 0x%x" % (gtype, ))
193
194        if res1 != 0:
195            raise StreamError("Reserved bits set in domain header 0x%04x" %
196                              (res1, ))
197
198        if page_shift != 12:
199            raise StreamError("Page shift expected to be 12.  Got %d" %
200                              (page_shift, ))
201
202        if major == 0:
203            self.info("Domain Header: legacy converted %s" %
204                      (dhdr_type_to_str[gtype], ))
205        else:
206            self.info("Domain Header: %s from Xen %d.%d" %
207                      (dhdr_type_to_str[gtype], major, minor))
208
209
210    def verify_record(self):
211        """ Verify an individual record """
212
213        rtype, length = self.unpack_exact(RH_FORMAT)
214
215        if rtype not in rec_type_to_str:
216            raise StreamError("Unrecognised record type 0x%x" % (rtype, ))
217
218        contentsz = (length + 7) & ~7
219        content = self.rdexact(contentsz)
220
221        if rtype != REC_TYPE_page_data:
222
223            if self.squashed_pagedata_records > 0:
224                self.info("Squashed %d Page Data records together" %
225                          (self.squashed_pagedata_records, ))
226                self.squashed_pagedata_records = 0
227
228            self.info("Libxc Record: %s, length %d" %
229                      (rec_type_to_str[rtype], length))
230
231        else:
232            self.squashed_pagedata_records += 1
233
234        padding = content[length:]
235        if padding != b"\x00" * len(padding):
236            raise StreamError("Padding containing non0 bytes found")
237
238        if rtype not in record_verifiers:
239            raise RuntimeError(
240                "No verification function for libxc record '%s'" %
241                rec_type_to_str[rtype])
242        else:
243            record_verifiers[rtype](self, content[:length])
244
245        return rtype
246
247
248    def verify_record_end(self, content):
249        """ End record """
250
251        if len(content) != 0:
252            raise RecordError("End record with non-zero length")
253
254
255    def verify_record_page_data(self, content):
256        """ Page Data record """
257        minsz = calcsize(PAGE_DATA_FORMAT)
258
259        if len(content) <= minsz:
260            raise RecordError(
261                "PAGE_DATA record must be at least %d bytes long" % (minsz, ))
262
263        count, res1 = unpack(PAGE_DATA_FORMAT, content[:minsz])
264
265        if res1 != 0:
266            raise StreamError(
267                "Reserved bits set in PAGE_DATA record 0x%04x" % (res1, ))
268
269        pfnsz = count * 8
270        if (len(content) - minsz) < pfnsz:
271            raise RecordError(
272                "PAGE_DATA record must contain a pfn record for each count")
273
274        pfns = list(unpack("=%dQ" % (count, ), content[minsz:minsz + pfnsz]))
275
276        nr_pages = 0
277        for idx, pfn in enumerate(pfns):
278
279            if pfn & PAGE_DATA_PFN_RESZ_MASK:
280                raise RecordError("Reserved bits set in pfn[%d]: 0x%016x" %
281                                  (idx, pfn & PAGE_DATA_PFN_RESZ_MASK))
282
283            if pfn >> PAGE_DATA_TYPE_SHIFT in (5, 6, 7, 8):
284                raise RecordError("Invalid type value in pfn[%d]: 0x%016x" %
285                                  (idx, pfn & PAGE_DATA_TYPE_LTAB_MASK))
286
287            # We expect page data for each normal page or pagetable
288            if PAGE_DATA_TYPE_NOTAB <= (pfn & PAGE_DATA_TYPE_LTABTYPE_MASK) \
289                    <= PAGE_DATA_TYPE_L4TAB:
290                nr_pages += 1
291
292        pagesz = nr_pages * 4096
293        if len(content) != minsz + pfnsz + pagesz:
294            raise RecordError("Expected %u + %u + %u, got %u" %
295                              (minsz, pfnsz, pagesz, len(content)))
296
297
298    def verify_record_x86_pv_info(self, content):
299        """ x86 PV Info record """
300
301        expectedsz = calcsize(X86_PV_INFO_FORMAT)
302        if len(content) != expectedsz:
303            raise RecordError("x86_pv_info: expected length of %d, got %d" %
304                              (expectedsz, len(content)))
305
306        width, levels, res1, res2 = unpack(X86_PV_INFO_FORMAT, content)
307
308        if width not in (4, 8):
309            raise RecordError("Expected width of 4 or 8, got %d" % (width, ))
310
311        if levels not in (3, 4):
312            raise RecordError("Expected levels of 3 or 4, got %d" % (levels, ))
313
314        if res1 != 0 or res2 != 0:
315            raise StreamError(
316                "Reserved bits set in X86_PV_INFO: 0x%04x 0x%08x" %
317                (res1, res2))
318
319        bitness = {4:32, 8:64}[width]
320        self.info("  %sbit guest, %d levels of pagetables" % (bitness, levels))
321
322
323    def verify_record_x86_pv_p2m_frames(self, content):
324        """ x86 PV p2m frames record """
325
326        if len(content) < 8:
327            raise RecordError("x86_pv_p2m_frames: record length must be at"
328                              " least 8 bytes long")
329
330        if len(content) % 8 != 0:
331            raise RecordError("Length expected to be a multiple of 8, not %d" %
332                              (len(content), ))
333
334        start, end = unpack("=II", content[:8])
335        self.info("  Start pfn 0x%x, End 0x%x" % (start, end))
336
337
338    def verify_record_x86_pv_vcpu_generic(self, content, name):
339        """ Generic for all REC_TYPE_x86_pv_vcpu_{basic,extended,xsave,msrs} """
340        minsz = calcsize(X86_PV_VCPU_HDR_FORMAT)
341
342        if len(content) < minsz:
343            raise RecordError(
344                "X86_PV_VCPU_%s record length must be at least %d bytes long" %
345                (name, minsz))
346
347        if len(content) == minsz:
348            self.info("Warning: X86_PV_VCPU_%s record with zero content" %
349                      (name, ))
350
351        vcpuid, res1 = unpack(X86_PV_VCPU_HDR_FORMAT, content[:minsz])
352
353        if res1 != 0:
354            raise StreamError(
355                "Reserved bits set in x86_pv_vcpu_%s record 0x%04x" %
356                (name, res1))
357
358        self.info("  vcpu%d %s context, %d bytes" %
359                  (vcpuid, name, len(content) - minsz))
360
361
362    def verify_record_shared_info(self, content):
363        """ shared info record """
364
365        contentsz = len(content)
366        if contentsz != 4096:
367            raise RecordError("Length expected to be 4906 bytes, not %d" %
368                              (contentsz, ))
369
370
371    def verify_record_tsc_info(self, content):
372        """ tsc info record """
373
374        sz = calcsize(X86_TSC_INFO_FORMAT)
375
376        if len(content) != sz:
377            raise RecordError("Length should be %u bytes" % (sz, ))
378
379        mode, khz, nsec, incarn, res1 = unpack(X86_TSC_INFO_FORMAT, content)
380
381        if res1 != 0:
382            raise StreamError("Reserved bits set in X86_TSC_INFO: 0x%08x" %
383                              (res1, ))
384
385        self.info("  Mode %u, %u kHz, %u ns, incarnation %d" %
386                  (mode, khz, nsec, incarn))
387
388
389    def verify_record_hvm_context(self, content):
390        """ hvm context record """
391
392        if len(content) == 0:
393            raise RecordError("Zero length HVM context")
394
395
396    def verify_record_hvm_params(self, content):
397        """ hvm params record """
398
399        sz = calcsize(HVM_PARAMS_FORMAT)
400
401        if len(content) < sz:
402            raise RecordError("Length should be at least %u bytes" % (sz, ))
403
404        count, rsvd = unpack(HVM_PARAMS_FORMAT, content[:sz])
405
406        if rsvd != 0:
407            raise RecordError("Reserved field not zero (0x%04x)" % (rsvd, ))
408
409        if count == 0:
410            self.info("Warning: HVM_PARAMS record with zero content")
411
412        sz += count * calcsize(HVM_PARAMS_ENTRY_FORMAT)
413
414        if len(content) != sz:
415            raise RecordError("Length should be %u bytes" % (sz, ))
416
417
418    def verify_record_toolstack(self, _):
419        """ toolstack record """
420        raise DeprecationWarning("Found Toolstack record in stream")
421
422
423    def verify_record_verify(self, content):
424        """ verify record """
425
426        if len(content) != 0:
427            raise RecordError("Verify record with non-zero length")
428
429
430    def verify_record_checkpoint(self, content):
431        """ checkpoint record """
432
433        if len(content) != 0:
434            raise RecordError("Checkpoint record with non-zero length")
435
436
437    def verify_record_checkpoint_dirty_pfn_list(self, content):
438        """ checkpoint dirty pfn list """
439        raise RecordError("Found checkpoint dirty pfn list record in stream")
440
441
442    def verify_record_static_data_end(self, content):
443        """ static data end record """
444
445        if len(content) != 0:
446            raise RecordError("End record with non-zero length")
447
448        if self.version < 3:
449            raise RecordError("Static data end record found in v2 stream")
450
451
452    def verify_record_x86_cpuid_policy(self, content):
453        """ x86 CPUID policy record """
454
455        if self.version < 3:
456            raise RecordError("x86 CPUID policy record found in v2 stream")
457
458        sz = calcsize(X86_CPUID_POLICY_FORMAT)
459        contentsz = len(content)
460
461        if contentsz < sz or (contentsz % sz) != 0:
462            raise RecordError("Record length %u, expected multiple of %u" %
463                              (contentsz, sz))
464
465
466    def verify_record_x86_msr_policy(self, content):
467        """ x86 MSR policy record """
468
469        if self.version < 3:
470            raise RecordError("x86 MSR policy record found in v2 stream")
471
472        sz = calcsize(X86_MSR_POLICY_FORMAT)
473        contentsz = len(content)
474
475        if contentsz < sz or (contentsz % sz) != 0:
476            raise RecordError("Record length %u, expected multiple of %u" %
477                              (contentsz, sz))
478
479
480record_verifiers = {
481    REC_TYPE_end:
482        VerifyLibxc.verify_record_end,
483    REC_TYPE_page_data:
484        VerifyLibxc.verify_record_page_data,
485
486    REC_TYPE_x86_pv_info:
487        VerifyLibxc.verify_record_x86_pv_info,
488    REC_TYPE_x86_pv_p2m_frames:
489        VerifyLibxc.verify_record_x86_pv_p2m_frames,
490
491    REC_TYPE_x86_pv_vcpu_basic:
492        lambda s, x:
493        VerifyLibxc.verify_record_x86_pv_vcpu_generic(s, x, "basic"),
494    REC_TYPE_x86_pv_vcpu_extended:
495        lambda s, x:
496        VerifyLibxc.verify_record_x86_pv_vcpu_generic(s, x, "extended"),
497    REC_TYPE_x86_pv_vcpu_xsave:
498        lambda s, x:
499        VerifyLibxc.verify_record_x86_pv_vcpu_generic(s, x, "xsave"),
500    REC_TYPE_x86_pv_vcpu_msrs:
501        lambda s, x:
502        VerifyLibxc.verify_record_x86_pv_vcpu_generic(s, x, "msrs"),
503
504    REC_TYPE_shared_info:
505        VerifyLibxc.verify_record_shared_info,
506    REC_TYPE_tsc_info:
507        VerifyLibxc.verify_record_tsc_info,
508
509    REC_TYPE_hvm_context:
510        VerifyLibxc.verify_record_hvm_context,
511    REC_TYPE_hvm_params:
512        VerifyLibxc.verify_record_hvm_params,
513    REC_TYPE_toolstack:
514        VerifyLibxc.verify_record_toolstack,
515    REC_TYPE_verify:
516        VerifyLibxc.verify_record_verify,
517    REC_TYPE_checkpoint:
518        VerifyLibxc.verify_record_checkpoint,
519    REC_TYPE_checkpoint_dirty_pfn_list:
520        VerifyLibxc.verify_record_checkpoint_dirty_pfn_list,
521
522    REC_TYPE_static_data_end:
523        VerifyLibxc.verify_record_static_data_end,
524
525    REC_TYPE_x86_cpuid_policy:
526        VerifyLibxc.verify_record_x86_cpuid_policy,
527    REC_TYPE_x86_msr_policy:
528        VerifyLibxc.verify_record_x86_msr_policy,
529    }
530