From f06f3ffd5ec41b9309755ab3537e46002034b980 Mon Sep 17 00:00:00 2001 From: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com> Date: Sat, 31 Oct 2026 10:52:58 -0500 Subject: [PATCH] fix(verify/sev_snp,tpm): enforce canonical lowercase hex in measurement checks and validate report_data_hex (#811) Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com> --- src/cmcp_verify/sev_snp.py | 17 ++++++++++++++--- src/cmcp_verify/tpm.py | 10 ++-------- tests/unit/test_sev_snp_verify.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/cmcp_verify/sev_snp.py b/src/cmcp_verify/sev_snp.py index 16353567..2a7a5ec0 100644 --- a/src/cmcp_verify/sev_snp.py +++ b/src/cmcp_verify/sev_snp.py @@ -139,7 +139,7 @@ def verify_sev_snp_measurement( result = SNPVerificationResult(verified=True) # Step 1: Format check - if not measurement.startswith("sha384:"): + if not isinstance(measurement, str) or not measurement.startswith("sha384:"): result.verified = False result.failure_reason = "invalid_measurement_format" result.unverified_fields.append("vcek_cert_chain") @@ -147,7 +147,7 @@ def verify_sev_snp_measurement( return result hex_part = measurement[len("sha384:"):] - if len(hex_part) != 96: + if len(hex_part) != 96 or any(c not in "0123456789abcdef" for c in hex_part): result.verified = False result.failure_reason = "invalid_measurement_format" result.unverified_fields.append("vcek_cert_chain") @@ -200,8 +200,19 @@ def verify_sev_snp_measurement( # silently ignoring a mismatch would accept an SNP report for a # different enclave whose measurement happens to match. if report_data_hex is not None: + if ( + not isinstance(report_data_hex, str) + or not report_data_hex + or len(report_data_hex) > 128 + or len(report_data_hex) % 2 != 0 + or any(c not in "0123456789abcdefABCDEF" for c in report_data_hex) + ): + result.verified = False + result.failure_reason = "report_data_mismatch" + result.unverified_fields.append("vcek_cert_chain") + return result extracted_rd = report.report_data - expected_rd = bytes.fromhex(report_data_hex[:128]) + expected_rd = bytes.fromhex(report_data_hex) # Pad expected to 64 bytes if shorter if len(expected_rd) < 64: expected_rd = expected_rd + b"\x00" * (64 - len(expected_rd)) diff --git a/src/cmcp_verify/tpm.py b/src/cmcp_verify/tpm.py index 8309053e..6449d1e2 100644 --- a/src/cmcp_verify/tpm.py +++ b/src/cmcp_verify/tpm.py @@ -156,16 +156,10 @@ def verify_tpm_measurement( def _valid_measurement(measurement: str) -> bool: """Return True if measurement is "sha256:" followed by exactly 64 hex characters.""" - if not measurement.startswith("sha256:"): + if not isinstance(measurement, str) or not measurement.startswith("sha256:"): return False hex_part = measurement[len("sha256:"):] - if len(hex_part) != 64: - return False - try: - bytes.fromhex(hex_part) - except ValueError: - return False - return True + return len(hex_part) == 64 and all(c in "0123456789abcdef" for c in hex_part) def _parse_tpm2b_attest( diff --git a/tests/unit/test_sev_snp_verify.py b/tests/unit/test_sev_snp_verify.py index e64f9d27..dad37951 100644 --- a/tests/unit/test_sev_snp_verify.py +++ b/tests/unit/test_sev_snp_verify.py @@ -213,3 +213,33 @@ def test_vcek_cert_chain_unverified_on_format_failure(): bad = "sha256:" + "z" * 64 result = verify_sev_snp_measurement(bad, raw_evidence=None) assert "vcek_cert_chain" in result.unverified_fields + + +def test_sev_snp_and_tpm_reject_non_hex_uppercase_and_oversized_report_data(): + import pytest + + from cmcp_verify.tpm import verify_tpm_measurement + + for bad_m in ("sha384:" + "z" * 96, "sha384:" + "A" * 96, "sha384: " + "a" * 94 + " ", None): + res = verify_sev_snp_measurement(bad_m, raw_evidence=None) # type: ignore[arg-type] + assert res.verified is False + assert res.failure_reason == "invalid_measurement_format" + + raw_m = b"\x77" * 48 + nonce = b"\x44" * 64 + report = make_snp_report(version=2, measurement_bytes=raw_m, report_data=nonce) + measurement = "sha384:" + hashlib.sha384(raw_m).hexdigest() + for bad_rd in (nonce.hex() + "aa", "zz" * 32, "", 123): + res = verify_sev_snp_measurement( + measurement, + raw_evidence=report, + report_data_hex=bad_rd, # type: ignore[arg-type] + ) + assert res.verified is False + assert res.failure_reason == "report_data_mismatch" + + for bad_tpm in ("sha256:" + "A" * 64, "sha256: " + "a" * 62 + " ", None): + tpm_res = verify_tpm_measurement(bad_tpm, raw_evidence=None) # type: ignore[arg-type] + assert tpm_res.verified is False + assert tpm_res.failure_reason == "invalid_measurement_format" + _ = pytest