2014-09-17 22:56:52 +04:00
#
# Support for parsing libvirt's domcapabilities XML
#
# Copyright 2014 Red Hat, Inc.
#
2018-04-04 16:35:41 +03:00
# This work is licensed under the GNU GPLv2 or later.
2018-03-20 22:00:02 +03:00
# See the COPYING file in the top-level directory.
2014-09-17 22:56:52 +04:00
2015-02-18 23:16:48 +03:00
import re
2019-03-15 11:49:56 +03:00
import xml . etree . ElementTree as ET
2015-02-18 23:16:48 +03:00
2019-03-15 11:49:56 +03:00
import libvirt
from . domain import DomainCpu
2019-06-17 04:12:39 +03:00
from . logger import log
2015-04-22 21:44:52 +03:00
from . xmlbuilder import XMLBuilder , XMLChildProperty , XMLProperty
2014-09-17 22:56:52 +04:00
2018-04-03 18:03:32 +03:00
########################################
# Genering <enum> and <value> handling #
########################################
2014-09-17 22:56:52 +04:00
class _Value ( XMLBuilder ) :
2018-03-21 17:53:34 +03:00
XML_NAME = " value "
2014-09-17 22:56:52 +04:00
value = XMLProperty ( " . " )
class _HasValues ( XMLBuilder ) :
values = XMLChildProperty ( _Value )
def get_values ( self ) :
return [ v . value for v in self . values ]
class _Enum ( _HasValues ) :
2018-03-21 17:53:34 +03:00
XML_NAME = " enum "
2014-09-17 22:56:52 +04:00
name = XMLProperty ( " ./@name " )
class _CapsBlock ( _HasValues ) :
supported = XMLProperty ( " ./@supported " , is_yesno = True )
enums = XMLChildProperty ( _Enum )
def enum_names ( self ) :
return [ e . name for e in self . enums ]
def get_enum ( self , name ) :
d = dict ( ( e . name , e ) for e in self . enums )
return d [ name ]
def _make_capsblock ( xml_root_name ) :
2018-04-03 18:03:32 +03:00
"""
Build a class object representing a list of < enum > in the XML . For
example , domcapabilities may have a block like :
< graphics supported = ' yes ' >
< enum name = ' type ' >
< value > sdl < / value >
< value > vnc < / value >
< value > spice < / value >
< / enum >
< / graphics >
To build a class that tracks that whole < graphics > block , call this
like _make_capsblock ( " graphics " )
"""
2014-09-17 22:56:52 +04:00
class TmpClass ( _CapsBlock ) :
pass
2018-03-21 17:53:34 +03:00
setattr ( TmpClass , " XML_NAME " , xml_root_name )
2014-09-17 22:56:52 +04:00
return TmpClass
2019-06-11 18:41:59 +03:00
################################
# SEV launch security handling #
################################
class _SEV ( XMLBuilder ) :
XML_NAME = " sev "
2019-06-11 18:42:00 +03:00
supported = XMLProperty ( " ./@supported " , is_yesno = True )
2019-06-11 18:41:59 +03:00
cbitpos = XMLProperty ( " ./cbitpos " , is_int = True )
reducedPhysBits = XMLProperty ( " ./reducedPhysBits " , is_int = True )
2018-04-03 18:03:32 +03:00
#############################
# Misc toplevel XML classes #
#############################
2014-09-17 22:56:52 +04:00
class _OS ( _CapsBlock ) :
2018-03-21 17:53:34 +03:00
XML_NAME = " os "
2014-09-17 22:56:52 +04:00
loader = XMLChildProperty ( _make_capsblock ( " loader " ) , is_single = True )
class _Devices ( _CapsBlock ) :
2018-03-21 17:53:34 +03:00
XML_NAME = " devices "
2014-09-17 22:56:52 +04:00
hostdev = XMLChildProperty ( _make_capsblock ( " hostdev " ) , is_single = True )
disk = XMLChildProperty ( _make_capsblock ( " disk " ) , is_single = True )
2019-10-03 11:44:52 +03:00
video = XMLChildProperty ( _make_capsblock ( " video " ) , is_single = True )
2014-09-17 22:56:52 +04:00
2016-06-10 03:22:25 +03:00
class _Features ( _CapsBlock ) :
2018-03-21 17:53:34 +03:00
XML_NAME = " features "
2016-06-10 03:22:25 +03:00
gic = XMLChildProperty ( _make_capsblock ( " gic " ) , is_single = True )
2019-06-11 18:41:59 +03:00
sev = XMLChildProperty ( _SEV , is_single = True )
2016-06-10 03:22:25 +03:00
2018-04-03 18:03:32 +03:00
###############
# CPU classes #
###############
2018-03-28 22:45:29 +03:00
class _CPUModel ( XMLBuilder ) :
XML_NAME = " model "
model = XMLProperty ( " . " )
2019-03-14 16:10:26 +03:00
usable = XMLProperty ( " ./@usable " )
2018-10-14 00:47:31 +03:00
fallback = XMLProperty ( " ./@fallback " )
class _CPUFeature ( XMLBuilder ) :
XML_NAME = " feature "
name = XMLProperty ( " ./@name " )
policy = XMLProperty ( " ./@policy " )
2018-03-28 22:45:29 +03:00
class _CPUMode ( XMLBuilder ) :
XML_NAME = " mode "
name = XMLProperty ( " ./@name " )
2018-10-04 19:23:32 +03:00
supported = XMLProperty ( " ./@supported " , is_yesno = True )
2018-10-14 00:47:31 +03:00
vendor = XMLProperty ( " ./vendor " )
2018-03-28 22:45:29 +03:00
2018-10-14 00:47:31 +03:00
models = XMLChildProperty ( _CPUModel )
2018-03-28 22:45:29 +03:00
def get_model ( self , name ) :
for model in self . models :
if model . model == name :
return model
2018-10-14 00:47:31 +03:00
features = XMLChildProperty ( _CPUFeature )
2018-04-03 18:03:32 +03:00
2018-03-28 22:45:29 +03:00
class _CPU ( XMLBuilder ) :
XML_NAME = " cpu "
modes = XMLChildProperty ( _CPUMode )
def get_mode ( self , name ) :
for mode in self . modes :
if mode . name == name :
return mode
2018-04-03 18:03:32 +03:00
#################################
# DomainCapabilities main class #
#################################
2014-09-17 22:56:52 +04:00
class DomainCapabilities ( XMLBuilder ) :
2015-02-18 23:16:48 +03:00
@staticmethod
2015-02-22 18:01:02 +03:00
def build_from_params ( conn , emulator , arch , machine , hvtype ) :
2015-04-08 16:53:30 +03:00
xml = None
2019-06-07 23:06:52 +03:00
if conn . support . conn_domain_capabilities ( ) :
2015-04-08 16:53:30 +03:00
try :
xml = conn . getDomainCapabilities ( emulator , arch ,
machine , hvtype )
2020-01-27 18:06:18 +03:00
except Exception : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . debug ( " Error fetching domcapabilities XML " ,
2015-04-08 16:53:30 +03:00
exc_info = True )
if not xml :
2015-02-18 23:16:48 +03:00
# If not supported, just use a stub object
2015-02-22 18:01:02 +03:00
return DomainCapabilities ( conn )
return DomainCapabilities ( conn , parsexml = xml )
2015-02-18 23:16:48 +03:00
2015-02-22 18:01:02 +03:00
@staticmethod
def build_from_guest ( guest ) :
return DomainCapabilities . build_from_params ( guest . conn ,
2015-02-18 23:16:48 +03:00
guest . emulator , guest . os . arch , guest . os . machine , guest . type )
# Mapping of UEFI binary names to their associated architectures. We
# only use this info to do things automagically for the user, it shouldn't
# validate anything the user explicitly enters.
_uefi_arch_patterns = {
2018-08-08 18:55:29 +03:00
" i686 " : [
2019-12-14 14:48:32 +03:00
r " .*edk2-i386-.* \ .fd " , # upstream qemu
2018-09-29 20:59:19 +03:00
r " .*ovmf-ia32.* " , # fedora, gerd's firmware repo
2018-08-08 18:55:29 +03:00
] ,
2015-02-18 23:16:48 +03:00
" x86_64 " : [
2019-12-14 14:48:32 +03:00
r " .*edk2-x86_64-.* \ .fd " , # upstream qemu
2018-09-29 20:59:19 +03:00
r " .*OVMF_CODE \ .fd " , # RHEL
r " .*ovmf-x64/OVMF.* \ .fd " , # gerd's firmware repo
r " .*ovmf-x86_64-.* " , # SUSE
r " .*ovmf.* " , " .*OVMF.* " , # generic attempt at a catchall
2015-02-18 23:16:48 +03:00
] ,
" aarch64 " : [
2018-09-29 20:59:19 +03:00
r " .*AAVMF_CODE \ .fd " , # RHEL
r " .*aarch64/QEMU_EFI.* " , # gerd's firmware repo
r " .*aarch64.* " , # generic attempt at a catchall
2019-12-14 14:48:32 +03:00
r " .*edk2-aarch64-code \ .fd " , # upstream qemu
2015-02-18 23:16:48 +03:00
] ,
2018-08-08 18:55:29 +03:00
" armv7l " : [
2018-09-29 20:59:19 +03:00
r " .*arm/QEMU_EFI.* " , # fedora, gerd's firmware repo
2019-12-14 14:48:32 +03:00
r " .*edk2-arm-code \ .fd " # upstream qemu
2018-08-08 18:55:29 +03:00
] ,
2015-02-18 23:16:48 +03:00
}
2015-02-22 19:02:55 +03:00
def find_uefi_path_for_arch ( self ) :
2015-02-18 23:16:48 +03:00
"""
Search the loader paths for one that matches the passed arch
"""
2015-02-22 19:02:55 +03:00
if not self . arch_can_uefi ( ) :
2020-01-27 18:06:18 +03:00
return # pragma: no cover
2015-02-18 23:16:48 +03:00
2015-02-22 19:02:55 +03:00
patterns = self . _uefi_arch_patterns . get ( self . arch )
2015-02-18 23:16:48 +03:00
for pattern in patterns :
for path in [ v . value for v in self . os . loader . values ] :
if re . match ( pattern , path ) :
return path
2015-02-22 19:13:21 +03:00
def label_for_firmware_path ( self , path ) :
"""
Return a pretty label for passed path , based on if we know
about it or not
"""
if not path :
if self . arch in [ " i686 " , " x86_64 " ] :
return _ ( " BIOS " )
return _ ( " None " )
for arch , patterns in self . _uefi_arch_patterns . items ( ) :
for pattern in patterns :
if re . match ( pattern , path ) :
return ( _ ( " UEFI %(arch)s : %(path)s " ) %
{ " arch " : arch , " path " : path } )
return _ ( " Custom: %(path)s " % { " path " : path } )
2015-02-22 19:02:55 +03:00
def arch_can_uefi ( self ) :
2015-02-18 23:16:48 +03:00
"""
Return True if we know how to setup UEFI for the passed arch
"""
2017-10-11 14:35:46 +03:00
return self . arch in list ( self . _uefi_arch_patterns . keys ( ) )
2015-02-18 23:16:48 +03:00
def supports_uefi_xml ( self ) :
"""
Return True if libvirt advertises support for proper UEFI setup
"""
return ( " readonly " in self . os . loader . enum_names ( ) and
" yes " in self . os . loader . get_enum ( " readonly " ) . get_values ( ) )
2018-10-04 19:23:32 +03:00
def supports_safe_host_model ( self ) :
"""
Return True if domcaps reports support for cpu mode = host - model .
2020-06-03 16:14:31 +03:00
host - model in fact predates this support , however it wasn ' t
2019-03-13 16:18:14 +03:00
general purpose safe prior to domcaps advertisement .
2018-10-04 19:23:32 +03:00
"""
2019-04-11 15:57:15 +03:00
for m in self . cpu . modes :
if ( m . name == " host-model " and m . supported and
m . models [ 0 ] . fallback == " forbid " ) :
return True
return False
2018-10-04 19:23:32 +03:00
2019-03-15 11:49:29 +03:00
def get_cpu_models ( self ) :
models = [ ]
for m in self . cpu . modes :
if m . name == " custom " and m . supported :
for model in m . models :
if model . usable != " no " :
models . append ( model . model )
return models
2019-03-15 11:49:56 +03:00
def _convert_mode_to_cpu ( self , xml ) :
root = ET . fromstring ( xml )
root . tag = " cpu "
root . attrib = None
arch = ET . SubElement ( root , " arch " )
arch . text = self . arch
return ET . tostring ( root , encoding = " unicode " )
2019-03-29 12:25:23 +03:00
def _get_expanded_cpu ( self , mode ) :
2019-03-15 11:49:56 +03:00
cpuXML = self . _convert_mode_to_cpu ( mode . get_xml ( ) )
2019-11-12 22:16:57 +03:00
log . debug ( " Generated CPU XML for security flag baseline: \n %s " , cpuXML )
2019-03-15 11:49:56 +03:00
try :
expandedXML = self . conn . baselineHypervisorCPU (
self . path , self . arch , self . machine , self . domain , [ cpuXML ] ,
libvirt . VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES )
2019-10-22 14:33:12 +03:00
except ( libvirt . libvirtError , AttributeError ) :
2019-03-15 11:49:56 +03:00
expandedXML = self . conn . baselineCPU ( [ cpuXML ] ,
libvirt . VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES )
return DomainCpu ( self . conn , expandedXML )
2020-01-27 18:06:18 +03:00
def _lookup_cpu_security_features ( self ) :
ret = [ ]
2019-03-15 11:49:56 +03:00
sec_features = [
' spec-ctrl ' ,
' ssbd ' ,
' ibpb ' ,
2019-05-14 20:59:49 +03:00
' virt-ssbd ' ,
' md-clear ' ]
2019-03-15 11:49:56 +03:00
for m in self . cpu . modes :
if m . name != " host-model " or not m . supported :
2020-01-27 18:06:18 +03:00
continue # pragma: no cover
2019-03-15 11:49:56 +03:00
try :
2019-03-29 12:25:23 +03:00
cpu = self . _get_expanded_cpu ( m )
2020-01-27 18:06:18 +03:00
except libvirt . libvirtError as e : # pragma: no cover
2019-06-17 04:12:39 +03:00
log . warning ( _ ( " Failed to get expanded CPU XML: %s " ) , e )
2019-03-15 11:49:56 +03:00
break
for feature in cpu . features :
if feature . name in sec_features :
2020-01-27 18:06:18 +03:00
ret . append ( feature . name )
2019-03-15 11:49:56 +03:00
2020-01-27 18:06:18 +03:00
log . debug ( " Found host-model security features: %s " , ret )
return ret
_features = None
def get_cpu_security_features ( self ) :
if self . _features is None :
self . _features = self . _lookup_cpu_security_features ( ) or [ ]
2019-04-03 16:17:08 +03:00
return self . _features
2019-03-15 11:49:56 +03:00
2020-01-27 18:06:18 +03:00
2019-06-11 18:42:00 +03:00
def supports_sev_launch_security ( self ) :
"""
Returns False if either libvirt doesn ' t advertise support for SEV at
all ( < libvirt - 4.5 .0 ) or if it explicitly advertises it as unsupported
on the platform
"""
return bool ( self . features . sev . supported )
2015-02-18 23:16:48 +03:00
2019-10-03 11:47:15 +03:00
def supports_video_bochs ( self ) :
"""
Returns False if either libvirt or qemu do not have support to bochs
video type .
"""
models = self . devices . video . get_enum ( " modelType " ) . get_values ( )
return bool ( " bochs " in models )
2018-03-21 17:53:34 +03:00
XML_NAME = " domainCapabilities "
2014-09-17 22:56:52 +04:00
os = XMLChildProperty ( _OS , is_single = True )
2018-03-28 22:45:29 +03:00
cpu = XMLChildProperty ( _CPU , is_single = True )
2014-09-17 22:56:52 +04:00
devices = XMLChildProperty ( _Devices , is_single = True )
2018-10-04 19:22:22 +03:00
features = XMLChildProperty ( _Features , is_single = True )
2015-02-22 19:02:55 +03:00
arch = XMLProperty ( " ./arch " )
2018-10-04 19:22:22 +03:00
domain = XMLProperty ( " ./domain " )
machine = XMLProperty ( " ./machine " )
path = XMLProperty ( " ./path " )