Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
7 changes: 4 additions & 3 deletions attestation-service/docs/tcb_claims.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,10 @@ The following fields report the TD's current TCB level, as opposed to the level
## Azure SEV-SNP Confidential VM (az-snp-vtpm)

- `["az-snp-vtpm"].*`: claims inherit the fields from the [SEV-SNP](#amd-sev-snp) layout.
- `["az-tdx-vtpm"].tpm`: Object. TPM PCR values.
- `["az-tdx-vtpm"].tpm.pcr{01,..,n}`: String (hex). SHA256 PCR registers for the TEE's vTPM quote.
- `["az-tdx-vtpm"].tpm.init_data`: **Optional**. String (hex). The register used as initdata digest (PCR 8).
- `["az-snp-vtpm"].tpm`: Object. TPM PCR values.
- `["az-snp-vtpm"].tpm.pcr{01,..,n}`: String (hex). SHA256 PCR registers for the TEE's vTPM quote.
- `["az-snp-vtpm"].tpm.init_data`: **Optional**. String (hex). The register used as initdata digest (PCR 8).
- `["az-snp-vtpm"].uefi_event_logs`: **Optional**. Parsed per-event AAEL (Attestation Agent Eventlog) entries, present when the guest recorded runtime measurements (e.g. container image pulls). Replayed and matched against the PCR register each event actually targeted before being surfaced.

Note: The TD Report and TD Quote are fetched during early boot in this TEE. Kernel, Initrd and rootfs are measured into the vTPM's registers.

Expand Down
33 changes: 31 additions & 2 deletions deps/eventlog/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ pub struct ReferenceMeasurement {
pub index: u32,
pub algorithm: TcgAlgorithm,
pub reference: Vec<u8>,
/// Value to seed the replay accumulator with, instead of an all-zero
/// buffer. Some measurement registers (e.g. a vTPM DRTM PCR) reset to a
/// non-zero value before any event extends them. See Table 7 of
/// <https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf>.
/// Leave empty to fall back to an all-zero seed of the algorithm's digest
/// size.
pub initial_value: Vec<u8>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be an Option? Also, please provide some documentation for this nonzero reset claim.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@aramirez-capacity This non-zero case would cause a problem. If allow non-zero initial value, an malwared attester can fake this field with a non-zero value. Also, trim the eventlog items with only later "good parts", and squash previous "bad parts" into a initial_value here.

Now we assume all fields to be zero-initialized, thus no such attack could happen - as all eventlog entries should be included so reflect the PCR final value.

If we want this field, we should let the verifier explicitly know the expected value and check it. One way is to add this into the parsed claims and let the policy cover this. Also, I suggest to change default policy to assert these fields to be all zeros. This prevents users from inadvertently allowing malicious initial PCR values ​​due to a lack of configuration.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The non-zero initial value is part of the spec: https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf (see Table 7).
It's not an arbitrary value; it's either 0 or -1, depending on the PCR.
I added a mention in the comment.

@Xynnn007 Xynnn007 Aug 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh ok. I misunderstood this. Thanks for the explanation. Another question is: Is DRTM the always behavior for Azure thus 17~22 be reset to -1?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Confirmed there's no DRTM launch on Azure's SEV-SNP vTPM (az-snp-vtpm).
Here's a live quote showing PCR23 holds a real measurement while PCR17-22 still contain their initial values (0xFF):

"tpm": {
    "pcr00": "13177a6535badf19415c06589705dd5a1890f73545c4a9fef7acfe2c6177a2b7",
    "pcr01": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
    "pcr02": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
    "pcr03": "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969",
    "pcr04": "c35ccf52ab2960710d0b0d90fa81596654dbd25cd21912bb26b2debbf5f14209",
    "pcr05": "0a3d1a92e6542d7a6bc71d199e5fc37f69a5b302bd0f5b4a1a1b15e7f6fc7683",
    "pcr06": "f6a2145d299e1372a74a218637e18fc2f72d791bcd41cead5ffa3697ff64fbeb",
    "pcr07": "3b20e022416fdf61d72e4da32b4354781be3de0608116976d28ffdad8c341d2a",
    "pcr08": "b6bf8a1bd74334f4a79e1b19002af49f4789eb4b25211b0c86d8f01466f0ef42",
    "pcr09": "4282b879e0120876b9f9b3800cf4b72f6eb45178365041223da972fdd07bbbbd",
    "pcr10": "484b9766c2d0615ac214cbce90c36a0b81d89e6fa7f485bce3abc36b40fc7165",
    "pcr11": "04785baf77f36dee740f41bd6758bd2d0a7b48ef7f732d5d1f6268cba008f0f1",
    "pcr12": "f1a142c53586e7e2223ec74e5f4d1a4942956b1fd9ac78fafcdf85117aa345da",
    "pcr13": "0000000000000000000000000000000000000000000000000000000000000000",
    "pcr14": "306f9d8b94f17d93dc6e7cf8f5c79d652eb4c6c4d13de2dddc24af416e13ecaf",
    "pcr15": "0000000000000000000000000000000000000000000000000000000000000000",
    "pcr16": "0000000000000000000000000000000000000000000000000000000000000000",
    "pcr17": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "pcr18": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "pcr19": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "pcr20": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "pcr21": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "pcr22": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
    "pcr23": "dd21afeb2b2ee07d6754d2fe9be856813aa023e5875445bc46cb1234b7508d42"
  }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

cc @mkulke please help to check if this is expected from white box view.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It should be -1/0xff by the TCG standard, so that's expected I think:

grafik

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

since it's part of the tcg spec, I'd agree that extending the generic eventlog struct with initial_values is the correct thing to do

}

impl EventDetails {
Expand Down Expand Up @@ -123,7 +130,8 @@ impl CcEventLog {
let digest_map = collect_digests_by_index(&self.log);

for item in data.iter() {
let calculated_ccel_ccmr = replay(&digest_map, item.index, item.algorithm)?;
let calculated_ccel_ccmr =
replay(&digest_map, item.index, item.algorithm, &item.initial_value)?;
if calculated_ccel_ccmr != item.reference {
bail!(
"Eventlog does not pass measurement replay CC Event Log Measurement Register [index = {}]. Calculated value: {}, Given value: {}",
Expand Down Expand Up @@ -154,9 +162,24 @@ fn replay(
digest_map: &HashMap<u32, Vec<ElDigest>>,
index: u32,
alg: TcgAlgorithm,
initial_value: &[u8],
) -> Result<Vec<u8>> {
let digest_size = alg.get_digest_size(alg)?;
let mut materials = vec![0u8; digest_size];

if !initial_value.is_empty() && initial_value.len() != digest_size {
bail!(
"initial_value length {} does not match {:?} digest size {}",
initial_value.len(),
alg,
digest_size
);
}

let mut materials = if initial_value.is_empty() {
vec![0u8; digest_size]
} else {
initial_value.to_vec()
};

if let Some(digests) = digest_map.get(&index) {
for digest in digests.iter().filter(|d| d.alg == alg) {
Expand Down Expand Up @@ -455,21 +478,25 @@ mod tests {
index: 1,
algorithm: TcgAlgorithm::Sha384,
reference: hex::decode(rtmr0).unwrap(),
initial_value: vec![],
},
ReferenceMeasurement {
index: 2,
algorithm: TcgAlgorithm::Sha384,
reference: hex::decode(rtmr1).unwrap(),
initial_value: vec![],
},
ReferenceMeasurement {
index: 3,
algorithm: TcgAlgorithm::Sha384,
reference: hex::decode(rtmr2).unwrap(),
initial_value: vec![],
},
ReferenceMeasurement {
index: 4,
algorithm: TcgAlgorithm::Sha384,
reference: hex::decode(rtmr3).unwrap(),
initial_value: vec![],
},
];

Expand Down Expand Up @@ -508,11 +535,13 @@ mod tests {
index: 1,
algorithm: TcgAlgorithm::Sha384,
reference: hex::decode(rtmr0).unwrap(),
initial_value: vec![],
},
ReferenceMeasurement {
index: 2,
algorithm: TcgAlgorithm::Sha384,
reference: hex::decode(rtmr1).unwrap(),
initial_value: vec![],
},
];

Expand Down
1 change: 1 addition & 0 deletions deps/verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ sgx-verifier = [
]
az-snp-vtpm-verifier = [
"az-snp-vtpm",
"eventlog",
"serde_with",
"sev",
"snp-verifier",
Expand Down
12 changes: 12 additions & 0 deletions deps/verifier/src/az_snp_vtpm/compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ pub(super) struct EvidenceV2 {
pub(super) hcl_report: Vec<u8>,
#[serde_as(as = "Base64<UrlSafe>")]
pub(super) vcek: Vec<u8>,
/// Base64 encoded runtime eventlog (AAEL in TCG2 encoding), populated by
/// the guest attester once it has recorded runtime measurement events.
pub(super) cc_eventlog: Option<String>,
}

/// Versioned evidence wrapper - tries V1 first, falls back to V0
Expand Down Expand Up @@ -143,4 +146,13 @@ impl Evidence {
Evidence::V2(_) => 2,
}
}

/// Base64 encoded runtime eventlog (AAEL), if the attester recorded one.
/// Only present starting at evidence v2.
pub(super) fn cc_eventlog(&self) -> Option<&str> {
match self {
Evidence::V0(_) | Evidence::V1(_) => None,
Evidence::V2(v2) => v2.cc_eventlog.as_deref(),
}
}
}
Loading