1# 2# Support for parsing libvirt's domcapabilities XML 3# 4# Copyright 2014 Red Hat, Inc. 5# 6# This work is licensed under the GNU GPLv2 or later. 7# See the COPYING file in the top-level directory. 8 9import re 10import xml.etree.ElementTree as ET 11 12import libvirt 13 14from .domain import DomainCpu 15from .logger import log 16from .xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty 17 18 19######################################## 20# Genering <enum> and <value> handling # 21######################################## 22 23class _Value(XMLBuilder): 24 XML_NAME = "value" 25 value = XMLProperty(".") 26 27 28class _HasValues(XMLBuilder): 29 values = XMLChildProperty(_Value) 30 31 def get_values(self): 32 return [v.value for v in self.values] 33 34 35class _Enum(_HasValues): 36 XML_NAME = "enum" 37 name = XMLProperty("./@name") 38 39 40class _CapsBlock(_HasValues): 41 supported = XMLProperty("./@supported", is_yesno=True) 42 enums = XMLChildProperty(_Enum) 43 44 def enum_names(self): 45 return [e.name for e in self.enums] 46 47 def get_enum(self, name): 48 for enum in self.enums: 49 if enum.name == name: 50 return enum 51 # Didn't find a match. Could be talking to older libvirt, or 52 # driver with incomplete info. Return a stub enum 53 return _Enum(self.conn) 54 55 56def _make_capsblock(xml_root_name): 57 """ 58 Build a class object representing a list of <enum> in the XML. For 59 example, domcapabilities may have a block like: 60 61 <graphics supported='yes'> 62 <enum name='type'> 63 <value>sdl</value> 64 <value>vnc</value> 65 <value>spice</value> 66 </enum> 67 </graphics> 68 69 To build a class that tracks that whole <graphics> block, call this 70 like _make_capsblock("graphics") 71 """ 72 class TmpClass(_CapsBlock): 73 pass 74 setattr(TmpClass, "XML_NAME", xml_root_name) 75 return TmpClass 76 77 78################################ 79# SEV launch security handling # 80################################ 81 82class _SEV(XMLBuilder): 83 XML_NAME = "sev" 84 supported = XMLProperty("./@supported", is_yesno=True) 85 cbitpos = XMLProperty("./cbitpos", is_int=True) 86 reducedPhysBits = XMLProperty("./reducedPhysBits", is_int=True) 87 88 89############################# 90# Misc toplevel XML classes # 91############################# 92 93class _OS(_CapsBlock): 94 XML_NAME = "os" 95 loader = XMLChildProperty(_make_capsblock("loader"), is_single=True) 96 97 98class _Devices(_CapsBlock): 99 XML_NAME = "devices" 100 hostdev = XMLChildProperty(_make_capsblock("hostdev"), is_single=True) 101 disk = XMLChildProperty(_make_capsblock("disk"), is_single=True) 102 video = XMLChildProperty(_make_capsblock("video"), is_single=True) 103 104 105class _Features(_CapsBlock): 106 XML_NAME = "features" 107 gic = XMLChildProperty(_make_capsblock("gic"), is_single=True) 108 sev = XMLChildProperty(_SEV, is_single=True) 109 110 111############### 112# CPU classes # 113############### 114 115class _CPUModel(XMLBuilder): 116 XML_NAME = "model" 117 model = XMLProperty(".") 118 usable = XMLProperty("./@usable") 119 fallback = XMLProperty("./@fallback") 120 121 122class _CPUFeature(XMLBuilder): 123 XML_NAME = "feature" 124 name = XMLProperty("./@name") 125 policy = XMLProperty("./@policy") 126 127 128class _CPUMode(XMLBuilder): 129 XML_NAME = "mode" 130 name = XMLProperty("./@name") 131 supported = XMLProperty("./@supported", is_yesno=True) 132 vendor = XMLProperty("./vendor") 133 134 models = XMLChildProperty(_CPUModel) 135 def get_model(self, name): 136 for model in self.models: 137 if model.model == name: 138 return model 139 140 features = XMLChildProperty(_CPUFeature) 141 142 143class _CPU(XMLBuilder): 144 XML_NAME = "cpu" 145 modes = XMLChildProperty(_CPUMode) 146 147 def get_mode(self, name): 148 for mode in self.modes: 149 if mode.name == name: 150 return mode 151 152 153################################# 154# DomainCapabilities main class # 155################################# 156 157class DomainCapabilities(XMLBuilder): 158 @staticmethod 159 def build_from_params(conn, emulator, arch, machine, hvtype): 160 xml = None 161 if conn.support.conn_domain_capabilities(): 162 try: 163 xml = conn.getDomainCapabilities(emulator, arch, 164 machine, hvtype) 165 except Exception: # pragma: no cover 166 log.debug("Error fetching domcapabilities XML", 167 exc_info=True) 168 169 if not xml: 170 # If not supported, just use a stub object 171 return DomainCapabilities(conn) 172 return DomainCapabilities(conn, parsexml=xml) 173 174 @staticmethod 175 def build_from_guest(guest): 176 return DomainCapabilities.build_from_params(guest.conn, 177 guest.emulator, guest.os.arch, guest.os.machine, guest.type) 178 179 # Mapping of UEFI binary names to their associated architectures. We 180 # only use this info to do things automagically for the user, it shouldn't 181 # validate anything the user explicitly enters. 182 _uefi_arch_patterns = { 183 "i686": [ 184 r".*edk2-i386-.*\.fd", # upstream qemu 185 r".*ovmf-ia32.*", # fedora, gerd's firmware repo 186 ], 187 "x86_64": [ 188 r".*edk2-x86_64-.*\.fd", # upstream qemu 189 r".*OVMF_CODE\.fd", # RHEL 190 r".*ovmf-x64/OVMF.*\.fd", # gerd's firmware repo 191 r".*ovmf-x86_64-.*", # SUSE 192 r".*ovmf.*", ".*OVMF.*", # generic attempt at a catchall 193 ], 194 "aarch64": [ 195 r".*AAVMF_CODE\.fd", # RHEL 196 r".*aarch64/QEMU_EFI.*", # gerd's firmware repo 197 r".*aarch64.*", # generic attempt at a catchall 198 r".*edk2-aarch64-code\.fd", # upstream qemu 199 ], 200 "armv7l": [ 201 r".*AAVMF32_CODE\.fd", # Debian qemu-efi-arm package 202 r".*arm/QEMU_EFI.*", # fedora, gerd's firmware repo 203 r".*edk2-arm-code\.fd" # upstream qemu 204 ], 205 } 206 207 def find_uefi_path_for_arch(self): 208 """ 209 Search the loader paths for one that matches the passed arch 210 """ 211 if not self.arch_can_uefi(): 212 return # pragma: no cover 213 214 patterns = self._uefi_arch_patterns.get(self.arch) 215 for pattern in patterns: 216 for path in [v.value for v in self.os.loader.values]: 217 if re.match(pattern, path): 218 return path 219 220 def label_for_firmware_path(self, path): 221 """ 222 Return a pretty label for passed path, based on if we know 223 about it or not 224 """ 225 if not path: 226 if self.arch in ["i686", "x86_64"]: 227 return _("BIOS") 228 return _("None") 229 230 for arch, patterns in self._uefi_arch_patterns.items(): 231 for pattern in patterns: 232 if re.match(pattern, path): 233 return (_("UEFI %(arch)s: %(path)s") % 234 {"arch": arch, "path": path}) 235 236 return _("Custom: %(path)s" % {"path": path}) 237 238 def arch_can_uefi(self): 239 """ 240 Return True if we know how to setup UEFI for the passed arch 241 """ 242 return self.arch in list(self._uefi_arch_patterns.keys()) 243 244 def supports_uefi_xml(self): 245 """ 246 Return True if libvirt advertises support for proper UEFI setup 247 """ 248 return ("readonly" in self.os.loader.enum_names() and 249 "yes" in self.os.loader.get_enum("readonly").get_values()) 250 251 def supports_safe_host_model(self): 252 """ 253 Return True if domcaps reports support for cpu mode=host-model. 254 host-model in fact predates this support, however it wasn't 255 general purpose safe prior to domcaps advertisement. 256 """ 257 for m in self.cpu.modes: 258 if (m.name == "host-model" and m.supported and 259 m.models[0].fallback == "forbid"): 260 return True 261 return False 262 263 def get_cpu_models(self): 264 models = [] 265 266 for m in self.cpu.modes: 267 if m.name == "custom" and m.supported: 268 for model in m.models: 269 if model.usable != "no": 270 models.append(model.model) 271 272 return models 273 274 def _convert_mode_to_cpu(self, xml): 275 root = ET.fromstring(xml) 276 root.tag = "cpu" 277 root.attrib = {} 278 arch = ET.SubElement(root, "arch") 279 arch.text = self.arch 280 return ET.tostring(root, encoding="unicode") 281 282 def _get_expanded_cpu(self, mode): 283 cpuXML = self._convert_mode_to_cpu(mode.get_xml()) 284 log.debug("Generated CPU XML for security flag baseline:\n%s", cpuXML) 285 286 try: 287 expandedXML = self.conn.baselineHypervisorCPU( 288 self.path, self.arch, self.machine, self.domain, [cpuXML], 289 libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES) 290 except (libvirt.libvirtError, AttributeError): 291 expandedXML = self.conn.baselineCPU([cpuXML], 292 libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES) 293 294 return DomainCpu(self.conn, expandedXML) 295 296 def _lookup_cpu_security_features(self): 297 ret = [] 298 sec_features = [ 299 'spec-ctrl', 300 'ssbd', 301 'ibpb', 302 'virt-ssbd', 303 'md-clear'] 304 305 for m in self.cpu.modes: 306 if m.name != "host-model" or not m.supported: 307 continue # pragma: no cover 308 309 try: 310 cpu = self._get_expanded_cpu(m) 311 except libvirt.libvirtError as e: # pragma: no cover 312 log.warning(_("Failed to get expanded CPU XML: %s"), e) 313 break 314 315 for feature in cpu.features: 316 if feature.name in sec_features: 317 ret.append(feature.name) 318 319 log.debug("Found host-model security features: %s", ret) 320 return ret 321 322 _features = None 323 def get_cpu_security_features(self): 324 if self._features is None: 325 self._features = self._lookup_cpu_security_features() or [] 326 return self._features 327 328 329 def supports_sev_launch_security(self): 330 """ 331 Returns False if either libvirt doesn't advertise support for SEV at 332 all (< libvirt-4.5.0) or if it explicitly advertises it as unsupported 333 on the platform 334 """ 335 return bool(self.features.sev.supported) 336 337 def supports_video_bochs(self): 338 """ 339 Returns False if either libvirt or qemu do not have support to bochs 340 video type. 341 """ 342 models = self.devices.video.get_enum("modelType").get_values() 343 return bool("bochs" in models) 344 345 XML_NAME = "domainCapabilities" 346 os = XMLChildProperty(_OS, is_single=True) 347 cpu = XMLChildProperty(_CPU, is_single=True) 348 devices = XMLChildProperty(_Devices, is_single=True) 349 features = XMLChildProperty(_Features, is_single=True) 350 351 arch = XMLProperty("./arch") 352 domain = XMLProperty("./domain") 353 machine = XMLProperty("./machine") 354 path = XMLProperty("./path") 355