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