Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions libvirt/tests/cfg/cpu/secure_host_validate.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
- cpu.secure_launch.secure_host_validate:
type = secure_host_validate
start_vm = "no"
only x86_64
kernel_msg_cmd = "journalctl -b 0 -k"
variants:
- amd_sev:
cpu_vendor = "amd"
secure_feature = "sev"
cpu_flags = ["sev"]
req_kernel_opts = ["mem_encrypt=on", "amd_iommu=on"]
kvm_probe_module_force_load = yes
kvm_probe_module_parameters = "sev=1"
req_cmds = {"sevctl": {"package": "sevctl", "cmd_params": "ok"}, "virt-host-validate": {"package": "libvirt", "cmd_params": ""}}
sevctl_patterns = ['\[.*PASS.*\]\s+-\s+Secure\sEncrypted\sVirtualization\s+\(SEV\)']
virt-host-validate_patterns = ['QEMU: Checking for secure guest support\s+:\s+PASS\n\s*\(SEV']
- amd_sev_es:
cpu_vendor = "amd"
secure_feature = "sev-es"
cpu_flags = ["sev", "sev_es"]
req_kernel_opts = ["mem_encrypt=on", "amd_iommu=on"]
kvm_probe_module_force_load = yes
kvm_probe_module_parameters = "sev=1 sev-es=1"
req_cmds = {"sevctl": {"package": "sevctl", "cmd_params": "ok"}, "virt-host-validate": {"package": "libvirt", "cmd_params": ""}}
sevctl_patterns = ['\[.*PASS.*\]\s+-\s+Secure\sEncrypted\sVirtualization\s+\(SEV\)', '\[.*PASS.*\]\s+-\s+Encrypted\sState\s+\(SEV-ES\)']
virt-host-validate_patterns = ['QEMU: Checking for secure guest support\s+:\s+PASS\n\s*\(SEV']
- amd_sev_snp:
cpu_vendor = "amd"
secure_feature = "sev-snp"
cpu_flags = ["sev", "sev_es", "sev_snp"]
req_kernel_opts = ["mem_encrypt=on", "amd_iommu=on"]
kvm_probe_module_force_load = yes
kvm_probe_module_parameters = "sev=1 sev-es=1 sev-snp=1"
req_cmds = {"sevctl": {"package": "sevctl", "cmd_params": "ok"}, "virt-host-validate": {"package": "libvirt", "cmd_params": ""}}
sevctl_patterns = ['\[.*PASS.*\]\s+-\s+Secure\sEncrypted\sVirtualization\s+\(SEV\)', '\[.*PASS.*\]\s+-\s+Encrypted\sState\s+\(SEV-ES\)', '\[.*PASS.*\]\s+-\s+Secure Nested Paging\s+\(SEV-SNP\)']
virt-host-validate_patterns = ['QEMU: Checking for secure guest support\s+:\s+PASS\n\s*\(SEV-SNP\)']
- intel_sgx:
func_supported_since_libvirt_ver = (9, 0, 0)
cpu_vendor = "intel"
secure_feature = "sgx"
cpu_flags = ["sgx", "sgx_lc"]
kernel_msg_patterns = ["EPC section"]
domcap_xpath = [{'element_attrs':[".//features/sgx[@supported='yes']"]}, {'element_attrs':[".//features/sgx/flc"],'text':'yes'}, {'element_attrs':[".//features/sgx/sgx1"],'text':'yes'}, {'element_attrs':[".//features/sgx/sgx2"],'text':'yes'}, {'element_attrs':[".//features/sgx/section_size[@unit='KiB']"]}, {'element_attrs':[".//features/sgx/sections"]}]
- intel_tdx:
func_supported_since_libvirt_ver = (11, 10, 0)
cpu_vendor = "intel"
secure_feature = "tdx"
cpu_flags = ["tdx_host_platform"]
req_kernel_opts = ["nohibernate", "intel_iommu=on"]
kvm_probe_module_force_load = yes
kvm_probe_module_parameters = "tdx=1"
kvm_probe_module_ignored_parameters = vmentry_l1d_flush=not required
domcap_xpath = [{'element_attrs':[".//features/launchSecurity[@supported='yes']"]}, {'element_attrs':[".//features/launchSecurity/enum[@name='sectype']"]}, {'element_attrs':[".//features/launchSecurity/enum/value"],'text':'tdx'}]
req_cmds = {"virt-host-validate": {"package": "libvirt", "cmd_params": ""}}
virt-host-validate_patterns = ['QEMU: Checking for secure guest support\s+:\s+PASS\n\s*\(TDX\)']

110 changes: 110 additions & 0 deletions libvirt/tests/src/cpu/secure_host_validate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Copyright Red Hat
#
# SPDX-License-Identifier: GPL-2.0
#
# Author: Liang Cong <lcong@redhat.com>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from avocado.utils import cpu as cpu_utils
from avocado.utils import process

from virttest import cpu
from virttest import libvirt_version
from virttest import utils_misc
from virttest import utils_package
from virttest.libvirt_xml import domcapability_xml
from virttest.utils_libvirt import libvirt_vmxml
from virttest.utils_test import libvirt


def run(test, params, env):
"""
Verify below secure launch features are supported by the host:
1. AMD SEV, SEV-ES, SEV-SNP
2. INTEL SGX
3. INTEL TDX
"""
def validate_prerequisites():
"""
Verify secure launch prerequisites of the host.
"""
def _validate_cpu_vendor():
"""
Validate host CPU vendor matches the expected vendor specified
"""
exp_vendor = params.get("cpu_vendor")
act_vendor = cpu_utils.get_vendor()
if exp_vendor and exp_vendor != act_vendor:
test.cancel("%s is not supported on %s CPU." % (secure_feature, act_vendor))

def _validate_cpu_flag():
"""
Validate host CPU supports the required feature flags
"""
host_cpu_flags = set(cpu.get_cpu_flags())
cpu_flags = eval(params.get("cpu_flags", "[]"))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n libvirt/tests/src/cpu/secure_host_validate.py

Repository: autotest/tp-libvirt

Length of output: 4622


Replace eval() with safe literal parsing for cfg params.

These eval() calls parse config-provided strings as executable Python, enabling arbitrary code execution. Use ast.literal_eval() instead, which safely parses Python literals.

Suggested fix
+import ast
 from avocado.utils import cpu as cpu_utils
 from avocado.utils import process
@@
 def run(test, params, env):
@@
+    def _parse_literal_param(name, default):
+        raw = params.get(name, default)
+        try:
+            return ast.literal_eval(raw)
+        except (ValueError, SyntaxError) as err:
+            test.error("Invalid literal for '%s': %s (raw=%r)" % (name, err, raw))
+
     def validate_prerequisites():
@@
-            cpu_flags = eval(params.get("cpu_flags", "[]"))
+            cpu_flags = _parse_literal_param("cpu_flags", "[]")
@@
-            req_kernel_opts = eval(params.get("req_kernel_opts", "[]"))
+            req_kernel_opts = _parse_literal_param("req_kernel_opts", "[]")
@@
-        kernel_msg_patterns = eval(params.get("kernel_msg_patterns", "[]"))
+        kernel_msg_patterns = _parse_literal_param("kernel_msg_patterns", "[]")
@@
-                pkg_patterns = eval(params.get("%s_patterns" % cmd, "[]"))
+                pkg_patterns = _parse_literal_param("%s_patterns" % cmd, "[]")
@@
-        domcap_xpath = eval(params.get("domcap_xpath", "[]"))
+        domcap_xpath = _parse_literal_param("domcap_xpath", "[]")
@@
-    req_cmds = eval(params.get("req_cmds", "{}"))
+    req_cmds = _parse_literal_param("req_cmds", "{}")

Also applies to lines: 47, 75, 82, 90, 97

🧰 Tools
🪛 Ruff (0.15.2)

[error] 42-42: Use of possibly insecure function; consider using ast.literal_eval

(S307)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libvirt/tests/src/cpu/secure_host_validate.py` at line 42, Replace all unsafe
eval() calls used to parse config params (e.g. the cpu_flags parsing at
cpu_flags = eval(params.get("cpu_flags", "[]")) and the similar sites mentioned)
with ast.literal_eval to safely parse Python literals; add an import for the ast
module at the top of the file and change each occurrence (lines referenced: the
eval calls at cpu_flags and the other eval uses) to use
ast.literal_eval(params.get(..., default)) and keep the same default string
values.

if cpu_flags and not set(cpu_flags).issubset(host_cpu_flags):
test.cancel("%s requires cpu flags %s, but host cpu flags is %s." % (secure_feature, cpu_flags, host_cpu_flags))

def _validate_kernel_option():
"""
Validate required kernel options are present in kernel cmdline
"""
req_kernel_opts = eval(params.get("req_kernel_opts", "[]"))
ker_cml = utils_misc.get_ker_cmd()
if not all(opt in ker_cml for opt in req_kernel_opts):
test.cancel("%s requires kernel opt %s, but kernel cml is %s." % (secure_feature, req_kernel_opts, ker_cml))

_validate_cpu_vendor()
_validate_cpu_flag()
_validate_kernel_option()

def setup_test():
"""
Setup the environment:
1. install require packages
"""
if req_cmds:
req_pkgs = [cmd_info["package"] for cmd_info in req_cmds.values()]
if not utils_package.package_install(req_pkgs):
test.error("Fail to install packages '%s'" % req_pkgs)

def run_test():
"""
Test steps
1. Check expected kernel msg
2. Check specific tool output
3. Check virsh domcapabilities output
"""
test.log.info("TEST_STEP1: Check expected kernel msg")
kernel_msg_cmd = params.get("kernel_msg_cmd")
kernel_msg_patterns = eval(params.get("kernel_msg_patterns", "[]"))
result = process.run(kernel_msg_cmd, shell=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check if the file exists and examine the relevant lines
if [ -f libvirt/tests/src/cpu/secure_host_validate.py ]; then
  echo "=== File found. Checking lines around 76 and 85 ==="
  sed -n '70,90p' libvirt/tests/src/cpu/secure_host_validate.py | cat -n
else
  echo "File not found at libvirt/tests/src/cpu/secure_host_validate.py"
  # Try to find similar files
  find . -name "*secure_host_validate*" -type f 2>/dev/null | head -10
fi

Repository: autotest/tp-libvirt

Length of output: 1322


🏁 Script executed:

#!/bin/bash
# Search for shell=True usage in the file to verify the claim
echo "=== Checking for shell=True in the file ==="
rg -n 'shell=True' libvirt/tests/src/cpu/secure_host_validate.py 2>/dev/null || echo "Pattern not found or file not accessible"

Repository: autotest/tp-libvirt

Length of output: 231


🏁 Script executed:

#!/bin/bash
# Get file size and full context around the flagged lines
echo "=== Full file structure ==="
wc -l libvirt/tests/src/cpu/secure_host_validate.py 2>/dev/null || echo "Cannot determine file size"

# Look at broader context to understand command construction
echo "=== Context around command execution ==="
sed -n '1,100p' libvirt/tests/src/cpu/secure_host_validate.py | cat -n 2>/dev/null || echo "Cannot read file"

Repository: autotest/tp-libvirt

Length of output: 4719


Avoid shell=True for parameter-driven command execution.

Lines 76 and 85 use shell=True with configuration-driven commands (from params and cmd_info), unnecessarily expanding the injection surface and triggering Ruff's S604 security check.

Both calls can safely run without a shell. Replace shell=True with shell=False, and convert the string command at line 85 to a list using shlex.split():

Suggested fix
+import shlex
 from avocado.utils import cpu as cpu_utils
 from avocado.utils import process
@@
-        result = process.run(kernel_msg_cmd, shell=True)
+        result = process.run(kernel_msg_cmd, shell=False)
@@
-                    pkg_cmd = "%s %s" % (cmd, cmd_info["cmd_params"])
-                    result = process.run(pkg_cmd, shell=True)
+                    pkg_cmd = [cmd] + shlex.split(cmd_info.get("cmd_params", ""))
+                    result = process.run(pkg_cmd, shell=False)
🧰 Tools
🪛 Ruff (0.15.2)

[error] 76-76: Function call with shell=True parameter identified, security issue

(S604)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libvirt/tests/src/cpu/secure_host_validate.py` at line 76, The tests call
process.run(...) with shell=True for parameter-driven commands (see
kernel_msg_cmd and cmd_info); replace shell=True with shell=False and pass an
argv list instead of a single string: for kernel_msg_cmd build/ensure a list of
arguments (or use shlex.split(kernel_msg_cmd)) and for cmd_info convert the
string to a list via shlex.split(cmd_info) before calling process.run, keeping
other call sites and error handling unchanged.

libvirt.check_result(result, expected_match=kernel_msg_patterns)

test.log.info("TEST_STEP2: Check specific tool output")
if req_cmds:
for cmd, cmd_info in req_cmds.items():
pkg_patterns = eval(params.get("%s_patterns" % cmd, "[]"))
if pkg_patterns:
pkg_cmd = "%s %s" % (cmd, cmd_info["cmd_params"])
result = process.run(pkg_cmd, shell=True)
for p_pattern in pkg_patterns:
libvirt.check_result(result, expected_match=p_pattern)

test.log.info("TEST_STEP3: Check virsh domcapabilities output")
domcap_xpath = eval(params.get("domcap_xpath", "[]"))
if domcap_xpath:
domcap_xml = domcapability_xml.DomCapabilityXML()
libvirt_vmxml.check_guest_xml_by_xpaths(domcap_xml, domcap_xpath)

libvirt_version.is_libvirt_feature_supported(params)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find the file to understand the context
find . -name "secure_host_validate.py" -type f

Repository: autotest/tp-libvirt

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Search for is_libvirt_feature_supported function definition in the codebase
rg -n 'def is_libvirt_feature_supported' --type=py

Repository: autotest/tp-libvirt

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for how is_libvirt_feature_supported is used - looking for its behavior
rg -n -B 2 -A 2 'is_libvirt_feature_supported\(' --type=py | head -100

Repository: autotest/tp-libvirt

Length of output: 8209


🏁 Script executed:

#!/bin/bash
# Look at the context around line 104 in the file
find . -name "secure_host_validate.py" -type f -exec sed -n '95,115p' {} +

Repository: autotest/tp-libvirt

Length of output: 712


🏁 Script executed:

#!/bin/bash
# Find where libvirt_version module is defined
find . -name "libvirt_version.py" -type f

Repository: autotest/tp-libvirt

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Search for the function definition and examine its implementation
rg -n -A 10 'def is_libvirt_feature_supported' --type=py

Repository: autotest/tp-libvirt

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Examine the libvirt_version.py file to understand its structure and imports
head -100 ./provider/libvirt_version.py

Repository: autotest/tp-libvirt

Length of output: 1815


🏁 Script executed:

#!/bin/bash
# Search for is_libvirt_feature_supported in the libvirt_version.py file
rg -n 'is_libvirt_feature_supported' ./provider/libvirt_version.py

Repository: autotest/tp-libvirt

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check the imports in secure_host_validate.py to see where is_libvirt_feature_supported comes from
head -30 ./libvirt/tests/src/cpu/secure_host_validate.py

Repository: autotest/tp-libvirt

Length of output: 875


🏁 Script executed:

#!/bin/bash
# Search for imports of is_libvirt_feature_supported or where it's coming from
rg -n 'import.*is_libvirt_feature_supported|from.*import.*is_libvirt_feature_supported' --type=py

Repository: autotest/tp-libvirt

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check if libvirt_version is imported and how
rg -n 'import.*libvirt_version|from.*libvirt_version' ./libvirt/tests/src/cpu/secure_host_validate.py

Repository: autotest/tp-libvirt

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Check if TestSkip or similar exceptions are used in the test file
rg -n 'TestSkip|TestCancel|test.skip|skip_test' ./libvirt/tests/src/cpu/secure_host_validate.py

Repository: autotest/tp-libvirt

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Check how exceptions are typically handled in test files by looking at error handling patterns
rg -n 'except.*:' ./libvirt/tests/src/cpu/secure_host_validate.py | head -20

Repository: autotest/tp-libvirt

Length of output: 45


🌐 Web query:

virttest is_libvirt_feature_supported avocado-vt behavior

💡 Result:

In Avocado-VT (virttest), helpers named like is_libvirt_feature_supported(...) are generally meant to be feature gates: tests call them to decide whether to run a libvirt-dependent code path or SKIP/CANCEL early when the host/libvirt stack can’t support it (rather than failing mid-test). Avocado-VT’s libvirt backend is built around libvirt APIs plus virsh-driven capability probing. [1]

What “supported” usually means in this context is:

  • Libvirt advertises the capability via its XML capability queries (for overall driver capabilities, virConnectGetCapabilities() / virsh capabilities). [2]
  • Or (for domain/guest features) via Domain Capabilities (virsh domcapabilities), whose <features> entries are explicitly marked supported='yes'/'no' (and may also enumerate supported values). [3]
  • And/or a minimum libvirt version / hypervisor version and/or API presence check is satisfied (a common pattern in libvirt tooling is: try a direct API/function/flag probe first, then fall back to daemon+HV version checks; if the minimum isn’t met, return False). [4]

So, behavior you should expect at call sites is:

  • returns True only when the feature is actually available on the current connection/driver according to capability reporting and/or version/API checks;
  • returns False when the feature is missing, not advertised, or can’t be validated on that platform/connection—so tests can skip instead of failing.

Sources: Avocado-VT architecture overview (libvirt backend usage) [1]; libvirt capabilities XML (virConnectGetCapabilities) [2]; libvirt domain capabilities <features supported=...> [3]; example of the common “function probe then version gating” support-check pattern in libvirt-related tooling (virtinst) [4].

[1] (deepwiki.com)
[2] (libvirt.org)
[3] (libvirt.org)
[4] (sources.debian.org)

Citations:


Check the return value of is_libvirt_feature_supported(params) to skip the test when the feature is unsupported.

The is_libvirt_feature_supported() function returns a boolean indicating whether the libvirt feature is available on the host. The return value must be checked—if it returns False, the test should skip rather than proceed on unsupported libvirt versions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libvirt/tests/src/cpu/secure_host_validate.py` at line 104, The call to
libvirt_version.is_libvirt_feature_supported(params) in secure_host_validate.py
currently ignores its boolean result; update the test to capture the return
value and skip the test when it returns False (e.g., use the test framework's
skip/skipTest/pytest.skip mechanism) so the test does not run on unsupported
libvirt versions; ensure the check occurs before any feature-dependent logic and
include a clear skip message mentioning the unsupported feature and params.

secure_feature = params.get("secure_feature")
req_cmds = eval(params.get("req_cmds", "{}"))

validate_prerequisites()
setup_test()
run_test()