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