Skip to content

Commit b7dd0bb

Browse files
committed
feat(report,hyperv)!: functionality to get fresh reports on Azure CVMs
Signed-off-by: Takuma IMAMURA <209989118+hyperfinitism@users.noreply.github.com>
1 parent 7705faf commit b7dd0bb

File tree

6 files changed

+250
-191
lines changed

6 files changed

+250
-191
lines changed

src/hyperv/check.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// This file contains code for checking Hyper-V feature is present.
3+
4+
use std::arch::x86_64::__cpuid;
5+
6+
const CPUID_GET_HIGHEST_FUNCTION: u32 = 0x80000000;
7+
const CPUID_PROCESSOR_INFO_AND_FEATURE_BITS: u32 = 0x1;
8+
9+
const CPUID_FEATURE_HYPERVISOR: u32 = 1 << 31;
10+
11+
const CPUID_HYPERV_SIG: &str = "Microsoft Hv";
12+
const CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS: u32 = 0x40000000;
13+
const CPUID_HYPERV_FEATURES: u32 = 0x40000003;
14+
const CPUID_HYPERV_MIN: u32 = 0x40000005;
15+
const CPUID_HYPERV_MAX: u32 = 0x4000ffff;
16+
const CPUID_HYPERV_ISOLATION: u32 = 1 << 22;
17+
const CPUID_HYPERV_CPU_MANAGEMENT: u32 = 1 << 12;
18+
const CPUID_HYPERV_ISOLATION_CONFIG: u32 = 0x4000000C;
19+
const CPUID_HYPERV_ISOLATION_TYPE_MASK: u32 = 0xf;
20+
const CPUID_HYPERV_ISOLATION_TYPE_SNP: u32 = 2;
21+
22+
pub fn present() -> bool {
23+
let mut cpuid = unsafe { __cpuid(CPUID_PROCESSOR_INFO_AND_FEATURE_BITS) };
24+
if (cpuid.ecx & CPUID_FEATURE_HYPERVISOR) == 0 {
25+
return false;
26+
}
27+
28+
cpuid = unsafe { __cpuid(CPUID_GET_HIGHEST_FUNCTION) };
29+
if cpuid.eax < CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS {
30+
return false;
31+
}
32+
33+
cpuid = unsafe { __cpuid(CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS) };
34+
if cpuid.eax < CPUID_HYPERV_MIN || cpuid.eax > CPUID_HYPERV_MAX {
35+
return false;
36+
}
37+
38+
let mut sig: Vec<u8> = vec![];
39+
sig.append(&mut cpuid.ebx.to_le_bytes().to_vec());
40+
sig.append(&mut cpuid.ecx.to_le_bytes().to_vec());
41+
sig.append(&mut cpuid.edx.to_le_bytes().to_vec());
42+
43+
if sig != CPUID_HYPERV_SIG.as_bytes() {
44+
return false;
45+
}
46+
47+
cpuid = unsafe { __cpuid(CPUID_HYPERV_FEATURES) };
48+
49+
let isolated: bool = (cpuid.ebx & CPUID_HYPERV_ISOLATION) != 0;
50+
let managed: bool = (cpuid.ebx & CPUID_HYPERV_CPU_MANAGEMENT) != 0;
51+
52+
if !isolated || managed {
53+
return false;
54+
}
55+
56+
cpuid = unsafe { __cpuid(CPUID_HYPERV_ISOLATION_CONFIG) };
57+
let mask = cpuid.ebx & CPUID_HYPERV_ISOLATION_TYPE_MASK;
58+
let snp = CPUID_HYPERV_ISOLATION_TYPE_SNP;
59+
60+
if mask != snp {
61+
return false;
62+
}
63+
64+
true
65+
}

src/hyperv/mod.rs

Lines changed: 3 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,5 @@
11
// SPDX-License-Identifier: Apache-2.0
2-
// This file contains code related to Hyper-V integration (Hypervisor). It provides a flag (`hyperv::present`) indicating whether the SNP Guest is running within a Hyper-V guest environment.
32

4-
use super::*;
5-
6-
use std::arch::x86_64::__cpuid;
7-
use std::mem::size_of;
8-
9-
const CPUID_GET_HIGHEST_FUNCTION: u32 = 0x80000000;
10-
const CPUID_PROCESSOR_INFO_AND_FEATURE_BITS: u32 = 0x1;
11-
12-
const CPUID_FEATURE_HYPERVISOR: u32 = 1 << 31;
13-
14-
const CPUID_HYPERV_SIG: &str = "Microsoft Hv";
15-
const CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS: u32 = 0x40000000;
16-
const CPUID_HYPERV_FEATURES: u32 = 0x40000003;
17-
const CPUID_HYPERV_MIN: u32 = 0x40000005;
18-
const CPUID_HYPERV_MAX: u32 = 0x4000ffff;
19-
const CPUID_HYPERV_ISOLATION: u32 = 1 << 22;
20-
const CPUID_HYPERV_CPU_MANAGEMENT: u32 = 1 << 12;
21-
const CPUID_HYPERV_ISOLATION_CONFIG: u32 = 0x4000000C;
22-
const CPUID_HYPERV_ISOLATION_TYPE_MASK: u32 = 0xf;
23-
const CPUID_HYPERV_ISOLATION_TYPE_SNP: u32 = 2;
24-
25-
const RSV1_SIZE: usize = size_of::<u32>() * 8;
26-
const REPORT_SIZE: usize = 1184;
27-
const RSV2_SIZE: usize = size_of::<u32>() * 5;
28-
const TOTAL_SIZE: usize = RSV1_SIZE + REPORT_SIZE + RSV2_SIZE;
29-
const REPORT_RANGE: std::ops::Range<usize> = RSV1_SIZE..(RSV1_SIZE + REPORT_SIZE);
30-
31-
pub fn present() -> bool {
32-
let mut cpuid = unsafe { __cpuid(CPUID_PROCESSOR_INFO_AND_FEATURE_BITS) };
33-
if (cpuid.ecx & CPUID_FEATURE_HYPERVISOR) == 0 {
34-
return false;
35-
}
36-
37-
cpuid = unsafe { __cpuid(CPUID_GET_HIGHEST_FUNCTION) };
38-
if cpuid.eax < CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS {
39-
return false;
40-
}
41-
42-
cpuid = unsafe { __cpuid(CPUID_HYPERV_VENDOR_AND_MAX_FUNCTIONS) };
43-
if cpuid.eax < CPUID_HYPERV_MIN || cpuid.eax > CPUID_HYPERV_MAX {
44-
return false;
45-
}
46-
47-
let mut sig: Vec<u8> = vec![];
48-
sig.append(&mut cpuid.ebx.to_le_bytes().to_vec());
49-
sig.append(&mut cpuid.ecx.to_le_bytes().to_vec());
50-
sig.append(&mut cpuid.edx.to_le_bytes().to_vec());
51-
52-
if sig != CPUID_HYPERV_SIG.as_bytes() {
53-
return false;
54-
}
55-
56-
cpuid = unsafe { __cpuid(CPUID_HYPERV_FEATURES) };
57-
58-
let isolated: bool = (cpuid.ebx & CPUID_HYPERV_ISOLATION) != 0;
59-
let managed: bool = (cpuid.ebx & CPUID_HYPERV_CPU_MANAGEMENT) != 0;
60-
61-
if !isolated || managed {
62-
return false;
63-
}
64-
65-
cpuid = unsafe { __cpuid(CPUID_HYPERV_ISOLATION_CONFIG) };
66-
let mask = cpuid.ebx & CPUID_HYPERV_ISOLATION_TYPE_MASK;
67-
let snp = CPUID_HYPERV_ISOLATION_TYPE_SNP;
68-
69-
if mask != snp {
70-
return false;
71-
}
72-
73-
true
74-
}
75-
76-
pub mod report {
77-
use super::*;
78-
79-
use anyhow::{anyhow, Context};
80-
use serde::{Deserialize, Serialize};
81-
use sev::firmware::guest::AttestationReport;
82-
use tss_esapi::{
83-
abstraction::nv,
84-
handles::NvIndexTpmHandle,
85-
interface_types::{resource_handles::NvAuth, session_handles::AuthSession},
86-
tcti_ldr::{DeviceConfig, TctiNameConf},
87-
};
88-
89-
const VTPM_HCL_REPORT_NV_INDEX: u32 = 0x01400001;
90-
91-
#[repr(C)]
92-
#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
93-
struct Hcl {
94-
rsv1: [u32; 8],
95-
report: AttestationReport,
96-
rsv2: [u32; 5],
97-
}
98-
99-
pub fn get(vmpl: u32) -> Result<AttestationReport> {
100-
if vmpl > 0 {
101-
eprintln!("Warning: --vmpl argument was ignored because attestation report is pre-fetched at VMPL 0 and stored in vTPM.");
102-
}
103-
let bytes = tpm2_read().context("unable to read attestation report bytes from vTPM")?;
104-
105-
hcl_report(&bytes)
106-
}
107-
108-
fn tpm2_read() -> Result<Vec<u8>> {
109-
let handle = NvIndexTpmHandle::new(VTPM_HCL_REPORT_NV_INDEX)
110-
.context("unable to initialize TPM handle")?;
111-
let mut ctx = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default()))?;
112-
ctx.set_sessions((Some(AuthSession::Password), None, None));
113-
114-
nv::read_full(&mut ctx, NvAuth::Owner, handle)
115-
.context("unable to read non-volatile vTPM data")
116-
}
117-
118-
fn hcl_report(bytes: &[u8]) -> Result<AttestationReport> {
119-
if bytes.len() < TOTAL_SIZE {
120-
return Err(anyhow!(
121-
"HCL report size mismatch: expected at least {}, got {}",
122-
TOTAL_SIZE,
123-
bytes.len()
124-
));
125-
}
126-
127-
let report_bytes = &bytes[REPORT_RANGE];
128-
129-
AttestationReport::from_bytes(report_bytes)
130-
.context("Unable to convert HCL report bytes to AttestationReport")
131-
}
132-
}
3+
pub mod check;
4+
pub mod report;
5+
pub(crate) mod tpm;

src/hyperv/report.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// This file contains code for requesting attestation reports from vTPMs on Azure Confidential VMs.
3+
4+
use super::tpm;
5+
use anyhow::{anyhow, Context, Result};
6+
use sev::{firmware::guest::AttestationReport, parser::ByteParser};
7+
use tss_esapi::{
8+
abstraction::nv,
9+
handles::NvIndexTpmHandle,
10+
interface_types::{resource_handles::NvAuth, session_handles::AuthSession},
11+
tcti_ldr::{DeviceConfig, TctiNameConf},
12+
};
13+
14+
const VTPM_HCL_REPORT_NV_INDEX: u32 = 0x01400001;
15+
const VTPM_USER_DATA_NV_INDEX: u32 = 0x01400002;
16+
const VTPM_USER_DATA_SIZE: usize = 64;
17+
18+
const HCL_REPORT_HEADER_SIZE: usize = size_of::<u32>() * 8;
19+
const HW_REPORT_SIZE: usize = 1184;
20+
const REPORT_RANGE: std::ops::Range<usize> =
21+
HCL_REPORT_HEADER_SIZE..(HCL_REPORT_HEADER_SIZE + HW_REPORT_SIZE);
22+
23+
pub fn get(data: [u8; VTPM_USER_DATA_SIZE]) -> Result<AttestationReport> {
24+
write_user_data_to_vtpm(data).context("unable to write user data to vTPM")?;
25+
let hcl_report_bytes =
26+
read_hcl_report_from_vtpm().context("unable to read attestation report bytes from vTPM")?;
27+
if hcl_report_bytes.len() < HCL_REPORT_HEADER_SIZE + HW_REPORT_SIZE {
28+
return Err(anyhow!(
29+
"HCL report size mismatch: expected at least {}, got {}",
30+
HCL_REPORT_HEADER_SIZE + HW_REPORT_SIZE,
31+
hcl_report_bytes.len()
32+
));
33+
}
34+
let hw_report_bytes = &hcl_report_bytes[REPORT_RANGE];
35+
AttestationReport::from_bytes(hw_report_bytes)
36+
.context("unable to convert HCL report bytes to AttestationReport")
37+
}
38+
39+
fn read_hcl_report_from_vtpm() -> Result<Vec<u8>> {
40+
let handle = NvIndexTpmHandle::new(VTPM_HCL_REPORT_NV_INDEX)
41+
.context("unable to initialize TPM handle")?;
42+
let mut ctx = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default()))?;
43+
ctx.set_sessions((Some(AuthSession::Password), None, None));
44+
45+
nv::read_full(&mut ctx, NvAuth::Owner, handle).context("unable to read non-volatile vTPM data")
46+
}
47+
48+
fn write_user_data_to_vtpm(data: [u8; VTPM_USER_DATA_SIZE]) -> Result<()> {
49+
let mut ctx = tss_esapi::Context::new(TctiNameConf::Device(DeviceConfig::default()))?;
50+
ctx.set_sessions((Some(AuthSession::Password), None, None));
51+
52+
let handle = NvIndexTpmHandle::new(VTPM_USER_DATA_NV_INDEX)
53+
.context("unable to initialize TPM handle")?;
54+
55+
let result = tpm::find_nv_index(&mut ctx, handle)?;
56+
57+
if let Some((public, _)) = result {
58+
if public.data_size() as usize != VTPM_USER_DATA_SIZE {
59+
tpm::nv_undefine(&mut ctx, handle)?;
60+
tpm::nv_define(&mut ctx, handle, VTPM_USER_DATA_SIZE)?;
61+
}
62+
} else {
63+
tpm::nv_define(&mut ctx, handle, VTPM_USER_DATA_SIZE)?;
64+
}
65+
66+
tpm::nv_write(&mut ctx, handle, &data).context("unable to write data to NV index")?;
67+
68+
Ok(())
69+
}

src/hyperv/tpm.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// This file contains code for handling TPM 2.0.
3+
4+
use anyhow::{Context, Result};
5+
use tss_esapi::{
6+
abstraction::nv,
7+
attributes::NvIndexAttributesBuilder,
8+
handles::{NvIndexHandle, NvIndexTpmHandle},
9+
interface_types::{
10+
algorithm::HashingAlgorithm,
11+
resource_handles::{NvAuth, Provision},
12+
},
13+
structures::{MaxNvBuffer, Name, NvPublic, NvPublicBuilder},
14+
};
15+
16+
/// Find an NV index
17+
pub fn find_nv_index(
18+
ctx: &mut tss_esapi::Context,
19+
nv_index: NvIndexTpmHandle,
20+
) -> Result<Option<(NvPublic, Name)>> {
21+
let list = nv::list(ctx).context("unable to list NV indices")?;
22+
23+
let entry = list
24+
.into_iter()
25+
.find(|(public, _)| public.nv_index() == nv_index);
26+
27+
Ok(entry)
28+
}
29+
30+
/// Define a new NV index with the specified size
31+
pub fn nv_define(
32+
ctx: &mut tss_esapi::Context,
33+
handle: NvIndexTpmHandle,
34+
len: usize,
35+
) -> Result<NvIndexHandle> {
36+
let attributes = NvIndexAttributesBuilder::new()
37+
.with_owner_read(true)
38+
.with_owner_write(true)
39+
.build()
40+
.context("unable to build NV index attributes")?;
41+
42+
let nv_public = NvPublicBuilder::new()
43+
.with_nv_index(handle)
44+
.with_index_attributes(attributes)
45+
.with_index_name_algorithm(HashingAlgorithm::Sha256)
46+
.with_data_area_size(len)
47+
.build()
48+
.context("unable to build NV public structure")?;
49+
50+
let index = ctx
51+
.nv_define_space(Provision::Owner, None, nv_public)
52+
.context("unable to define NV index")?;
53+
54+
Ok(index)
55+
}
56+
57+
/// Undefine an existing NV index
58+
pub fn nv_undefine(ctx: &mut tss_esapi::Context, handle: NvIndexTpmHandle) -> Result<()> {
59+
let key_handle = ctx
60+
.execute_without_session(|c| c.tr_from_tpm_public(handle.into()))
61+
.context("unable to resolve NV index handle")?;
62+
let index = key_handle.into();
63+
ctx.nv_undefine_space(Provision::Owner, index)
64+
.context("unable to undefine NV index")
65+
}
66+
67+
/// Write data to an NV index
68+
pub fn nv_write(ctx: &mut tss_esapi::Context, handle: NvIndexTpmHandle, data: &[u8]) -> Result<()> {
69+
let buffer = MaxNvBuffer::try_from(data).context("unable to create MaxNvBuffer from data")?;
70+
let key_handle = ctx
71+
.execute_without_session(|c| c.tr_from_tpm_public(handle.into()))
72+
.context("unable to resolve NV index handle")?;
73+
let index = key_handle.into();
74+
ctx.nv_write(NvAuth::Owner, index, buffer, 0)
75+
.context("unable to write data to NV index")
76+
}

src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@ fn main() -> Result<()> {
7676
let snpguest = SnpGuest::parse();
7777

7878
#[cfg(feature = "hyperv")]
79-
let hv = hyperv::present();
79+
let azcvm_present = hyperv::check::present();
8080

8181
#[cfg(not(feature = "hyperv"))]
82-
let hv = false;
82+
let azcvm_present = false;
8383

8484
let status = match snpguest.cmd {
85-
SnpGuestCmd::Report(args) => report::get_report(args, hv),
85+
SnpGuestCmd::Report(args) => report::get_report(args, azcvm_present),
8686
SnpGuestCmd::Certificates(args) => certs::get_ext_certs(args),
8787
SnpGuestCmd::Fetch(subcmd) => fetch::cmd(subcmd),
8888
SnpGuestCmd::Verify(subcmd) => verify::cmd(subcmd, snpguest.quiet),

0 commit comments

Comments
 (0)