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