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