From 4b362d8dc9f0061e410e5f3fb15522c042bbc262 Mon Sep 17 00:00:00 2001 From: Arvind Kumar Date: Thu, 7 Mar 2024 20:58:31 +0000 Subject: [PATCH 1/2] Adding snpguest ok Signed-off-by: arvindskumar99 --- .gitignore | 3 +- Cargo.lock | 15 +++ Cargo.toml | 3 + src/main.rs | 5 + src/ok.rs | 285 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 src/ok.rs diff --git a/.gitignore b/.gitignore index 869df07..eb1d4ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -Cargo.lock \ No newline at end of file +Cargo.lock +.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9e96f25..54afd01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,6 +208,12 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12170080f3533d6f09a19f81596f836854d0fa4867dc32c8172b8474b4e9de61" +[[package]] +name = "colorful" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97af0562545a7d7f3d9222fcf909963bec36dcb502afaacab98c6ffac8da47ce" + [[package]] name = "core-foundation" version = "0.9.4" @@ -733,6 +739,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "msru" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a014208ef068fd9eed02eceb063ecba151d9922de4f8b4bb3703ff3d2a3eaa" + [[package]] name = "native-tls" version = "0.2.11" @@ -1385,8 +1397,11 @@ dependencies = [ "anyhow", "asn1-rs", "bincode", + "bitfield 0.13.2", + "colorful", "env_logger", "hex", + "msru", "nix", "openssl", "rand", diff --git a/Cargo.toml b/Cargo.toml index e3bc5e5..c153857 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,6 @@ x509-parser = { version="^0.14", features=["verify"] } asn1-rs = "0.5.2" rand = "0.8.5" tss-esapi = { version = "7.2", optional=true } +msru = "0.2.0" +colorful = "0.2.2" +bitfield = "0.13.2" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 1e219c6..2f8d986 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod certs; mod display; mod fetch; mod key; +mod ok; mod report; mod verify; @@ -54,6 +55,9 @@ enum SnpGuestCmd { #[structopt(about = "Key command to generate derived key.")] Key(KeyArgs), + + #[structopt(about = "Probe system for SEV-SNP support")] + Ok, } fn main() -> Result<()> { @@ -74,6 +78,7 @@ fn main() -> Result<()> { SnpGuestCmd::Verify(subcmd) => verify::cmd(subcmd, snpguest.quiet), SnpGuestCmd::Display(subcmd) => display::cmd(subcmd, snpguest.quiet), SnpGuestCmd::Key(args) => key::get_derived_key(args), + SnpGuestCmd::Ok => ok::cmd(snpguest.quiet), }; if let Err(ref e) = status { diff --git a/src/ok.rs b/src/ok.rs new file mode 100644 index 0000000..b2b818a --- /dev/null +++ b/src/ok.rs @@ -0,0 +1,285 @@ +use super::*; + +use std::fmt; + +use bitfield::bitfield; +use colorful::*; +use msru::*; +use serde::{Deserialize, Serialize}; + +const SEV_MASK: usize = 1; +const ES_MASK: usize = 1 << 1; +const SNP_MASK: usize = 1 << 2; +type TestFn = dyn Fn() -> TestResult; + +struct Test { + name: &'static str, + gen_mask: usize, + run: Box, + sub: Vec, +} + +struct TestResult { + name: String, + stat: TestState, + mesg: Option, +} + +#[derive(PartialEq, Eq)] +enum TestState { + Pass, + Skip, + Fail, +} + +bitfield! { + #[repr(C)] + #[derive(Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub struct BitRead(u64); + impl Debug; + pub sev_bit, _: 0,0; + pub es_bit, _: 1,1; + pub snp_bit, _:2,2; +} + +enum GuestLevels { + Sev, + SevEs, + Snp, +} + +impl fmt::Display for GuestLevels { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + GuestLevels::Sev => "SEV", + GuestLevels::SevEs => "SEV-ES", + GuestLevels::Snp => "SNP", + }; + write!(f, "{}", s) + } +} + +impl fmt::Display for TestState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + TestState::Pass => format!("{}", "PASS".green()), + TestState::Skip => format!("{}", "SKIP".yellow()), + TestState::Fail => format!("{}", "FAIL".red()), + }; + + write!(f, "{}", s) + } +} + +fn collect_tests() -> Vec { + let tests = vec![ + Test { + name: "SEV", + gen_mask: SEV_MASK, + run: Box::new(|| encryption_levels(GuestLevels::Sev)), + sub: vec![], + }, + Test { + name: "SEV-ES", + gen_mask: ES_MASK, + run: Box::new(|| encryption_levels(GuestLevels::SevEs)), + sub: vec![], + }, + Test { + name: "SNP", + gen_mask: SNP_MASK, + run: Box::new(|| encryption_levels(GuestLevels::Snp)), + sub: vec![], + }, + ]; + tests +} + +const INDENT: usize = 2; + +pub fn cmd(quiet: bool) -> Result<()> { + let tests = collect_tests(); + + if run_test(&tests, 0, quiet, SEV_MASK | ES_MASK | SNP_MASK) { + Ok(()) + } else { + Err(anyhow::anyhow!( + "One or more tests in sevctl-ok reported a failure" + )) + } +} + +fn run_test(tests: &[Test], level: usize, quiet: bool, mask: usize) -> bool { + let mut passed = true; + + for t in tests { + // Skip tests that aren't included in the specified generation. + if (t.gen_mask & mask) != t.gen_mask { + test_gen_not_included(t, level, quiet); + continue; + } + + let res = (t.run)(); + emit_result(&res, level, quiet); + match res.stat { + TestState::Pass => { + if !run_test(&t.sub, level + INDENT, quiet, mask) { + passed = false; + } + } + TestState::Fail => { + passed = false; + emit_skip(&t.sub, level + INDENT, quiet); + } + // Skipped tests are marked as skip before recursing. They are just emitted and not actually processed. + TestState::Skip => unreachable!(), + } + } + + passed +} + +fn emit_result(res: &TestResult, level: usize, quiet: bool) { + if !quiet { + let msg = match &res.mesg { + Some(m) => format!(": {}", m), + None => "".to_string(), + }; + println!( + "[ {:^4} ] {:width$}- {}{}", + format!("{}", res.stat), + "", + res.name, + msg, + width = level + ) + } +} + +fn test_gen_not_included(test: &Test, level: usize, quiet: bool) { + if !quiet { + let tr_skip = TestResult { + name: test.name.to_string(), + stat: TestState::Skip, + mesg: None, + }; + + println!( + "[ {:^4} ] {:width$}- {}", + format!("{}", tr_skip.stat), + "", + tr_skip.name, + width = level + ); + emit_skip(&test.sub, level + INDENT, quiet); + } +} + +fn emit_skip(tests: &[Test], level: usize, quiet: bool) { + if !quiet { + for t in tests { + let tr_skip = TestResult { + name: t.name.to_string(), + stat: TestState::Skip, + mesg: None, + }; + + println!( + "[ {:^4} ] {:width$}- {}", + format!("{}", tr_skip.stat), + "", + tr_skip.name, + width = level + ); + emit_skip(&t.sub, level + INDENT, quiet); + } + } +} + +fn get_values(reg: u32, cpu: u16) -> Result { + let mut msr = Msr::new(reg, cpu).context("Error Reading MSR")?; + let my_bitfield = BitRead(msr.read()?); + Ok(my_bitfield) +} + +fn encryption_levels(test: GuestLevels) -> TestResult { + let temp_bitfield = match get_values(0xC0010131, 0) { + Ok(temp_bitfield) => temp_bitfield, + Err(e) => { + return TestResult { + name: test.to_string(), + stat: TestState::Fail, + mesg: Some(format!("Failed to get bit values, {e}")), + } + } + }; + + match test { + GuestLevels::Sev => { + let sev_status = temp_bitfield.sev_bit(); + if sev_status == 1 { + TestResult { + name: format!("{}", GuestLevels::Sev), + stat: TestState::Pass, + mesg: Some("SEV is ENABLED".to_string()), + } + } else if sev_status == 0 { + TestResult { + name: format!("{}", GuestLevels::Sev), + stat: TestState::Fail, + mesg: Some("SEV is DISABLED".to_string()), + } + } else { + TestResult { + name: format!("{}", GuestLevels::Sev), + stat: TestState::Fail, + mesg: format!("Invalid value found in MSR, {}", sev_status).into(), + } + } + } + GuestLevels::SevEs => { + let sev_es_status = temp_bitfield.es_bit(); + if sev_es_status == 1 { + TestResult { + name: format!("{}", GuestLevels::SevEs), + stat: TestState::Pass, + mesg: Some("SEV-ES is ENABLED".to_string()), + } + } else if sev_es_status == 0 { + TestResult { + name: format!("{}", GuestLevels::SevEs), + stat: TestState::Fail, + mesg: Some("SEV-ES is DISABLED".to_string()), + } + } else { + TestResult { + name: format!("{}", GuestLevels::SevEs), + stat: TestState::Fail, + mesg: format!("Invalid value found in MSR, {}", sev_es_status).into(), + } + } + } + GuestLevels::Snp => { + let snp_status = temp_bitfield.snp_bit(); + if snp_status == 1 { + TestResult { + name: format!("{}", GuestLevels::Snp), + stat: TestState::Pass, + mesg: Some("SNP is ENABLED".to_string()), + } + } else if snp_status == 0 { + TestResult { + name: format!("{}", GuestLevels::Snp), + stat: TestState::Fail, + mesg: Some("SNP is DISABLED".to_string()), + } + } else { + TestResult { + name: format!("{}", GuestLevels::Snp), + stat: TestState::Fail, + mesg: format!("Invalid value found in MSR, {}", snp_status).into(), + } + } + } + } +} From a01a3602066f5dd0683470e1b0cd4155cc11ab3d Mon Sep 17 00:00:00 2001 From: arvindskumar99 Date: Wed, 13 Mar 2024 19:39:07 +0000 Subject: [PATCH 2/2] Additional Features Signed-off-by: arvindskumar99 --- Cargo.toml | 2 +- src/ok.rs | 295 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 191 insertions(+), 106 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c153857..6465c16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,4 +35,4 @@ rand = "0.8.5" tss-esapi = { version = "7.2", optional=true } msru = "0.2.0" colorful = "0.2.2" -bitfield = "0.13.2" \ No newline at end of file +bitfield = "0.13.2" diff --git a/src/ok.rs b/src/ok.rs index b2b818a..eb82d9e 100644 --- a/src/ok.rs +++ b/src/ok.rs @@ -35,28 +35,27 @@ enum TestState { bitfield! { #[repr(C)] #[derive(Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] - pub struct BitRead(u64); + pub struct SevStatus(u64); impl Debug; - pub sev_bit, _: 0,0; - pub es_bit, _: 1,1; - pub snp_bit, _:2,2; -} - -enum GuestLevels { - Sev, - SevEs, - Snp, -} - -impl fmt::Display for GuestLevels { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - GuestLevels::Sev => "SEV", - GuestLevels::SevEs => "SEV-ES", - GuestLevels::Snp => "SNP", - }; - write!(f, "{}", s) - } + pub sev_bit, _ : 0,0; + pub es_bit, _ : 1,1; + pub snp_bit, _ : 2,2; + pub vtom_bit, _ : 3,3; + pub reflectvc_bit, _ : 4,4; + pub restricted_injection_bit, _ : 5,5; + pub alternate_injection_bit, _ : 6,6; + pub debug_swap_bit, _ : 7,7; + pub prevent_host_ibs_bit, _ : 8,8; + pub btb_isolation_bit, _ : 9,9; + pub vmpl_sss_bit, _ : 10,10; + pub secure_tse_bit, _ : 11,11; + pub vmg_exit_parameter_bit, _ : 12,12; + reserved_1, _ : 13, 13; + pub ibs_virtualization_bit, _ : 14,14; + reserved_2, _ : 15,15; + pub vmsa_reg_prot_bit, _ : 16,16; + pub smt_protection_bit, _ : 17, 17; + reserved_3, _ : 18, 63; } impl fmt::Display for TestState { @@ -72,25 +71,175 @@ impl fmt::Display for TestState { } fn collect_tests() -> Vec { + // Grab your MSR value one time. + let temp_bitfield = match get_values(0xC0010131, 0) { + Ok(temp_bitfield) => temp_bitfield, + Err(e) => { + return vec![Test { + name: "Error reading MSR", + gen_mask: SEV_MASK, + run: Box::new(move || TestResult { + name: "Error reading MSR".to_string(), + stat: TestState::Fail, + mesg: Some(format!("Failed to get bit values, {e}")), + }), + sub: vec![], + }] + } + }; + let tests = vec![ Test { name: "SEV", gen_mask: SEV_MASK, - run: Box::new(|| encryption_levels(GuestLevels::Sev)), + run: Box::new(move || run_msr_check(temp_bitfield.sev_bit(), "SEV", false)), sub: vec![], }, Test { name: "SEV-ES", gen_mask: ES_MASK, - run: Box::new(|| encryption_levels(GuestLevels::SevEs)), + run: Box::new(move || run_msr_check(temp_bitfield.es_bit(), "SEV-ES", false)), sub: vec![], }, Test { name: "SNP", gen_mask: SNP_MASK, - run: Box::new(|| encryption_levels(GuestLevels::Snp)), + run: Box::new(move || run_msr_check(temp_bitfield.snp_bit(), "SNP", false)), sub: vec![], }, + Test { + name: "Optional Features", + gen_mask: SEV_MASK, + run: Box::new(|| TestResult { + name: "Optional Features statuses:".to_string(), + stat: TestState::Pass, + mesg: None, + }), + sub: vec![ + Test { + name: "vTOM", + gen_mask: SNP_MASK, + run: Box::new(move || run_msr_check(temp_bitfield.vtom_bit(), "VTOM", true)), + sub: vec![], + }, + Test { + name: "Reflect VC", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check(temp_bitfield.reflectvc_bit(), "ReflectVC", true) + }), + sub: vec![], + }, + Test { + name: "Restricted Injection", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check( + temp_bitfield.restricted_injection_bit(), + "Restricted Injection", + true, + ) + }), + sub: vec![], + }, + Test { + name: "Alternate Injection", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check( + temp_bitfield.alternate_injection_bit(), + "Alternate Injection", + true, + ) + }), + sub: vec![], + }, + Test { + name: "Debug Swap", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check(temp_bitfield.debug_swap_bit(), "Debug Swap", true) + }), + sub: vec![], + }, + Test { + name: "Prevent Host IBS", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check( + temp_bitfield.prevent_host_ibs_bit(), + "Prevent Host IBS", + true, + ) + }), + sub: vec![], + }, + Test { + name: "SNP BTB Isolation", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check(temp_bitfield.btb_isolation_bit(), "SNP BTB Isolation", true) + }), + sub: vec![], + }, + Test { + name: "VMPL SSS", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check(temp_bitfield.vmpl_sss_bit(), "VMPL SSS", true) + }), + sub: vec![], + }, + Test { + name: "Secure TSE", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check(temp_bitfield.secure_tse_bit(), "Secure TSE", true) + }), + sub: vec![], + }, + Test { + name: "VMG Exit Parameter", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check( + temp_bitfield.vmg_exit_parameter_bit(), + "VMG Exit Parameter", + true, + ) + }), + sub: vec![], + }, + Test { + name: "IBS Virtualization", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check( + temp_bitfield.ibs_virtualization_bit(), + "IBS Virtualization", + true, + ) + }), + sub: vec![], + }, + Test { + name: "VMSA Reg Prot", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check(temp_bitfield.vmsa_reg_prot_bit(), "VMSA Reg Prot", true) + }), + sub: vec![], + }, + Test { + name: "SMT Protection", + gen_mask: SNP_MASK, + run: Box::new(move || { + run_msr_check(temp_bitfield.smt_protection_bit(), "SMT Protection", true) + }), + sub: vec![], + }, + ], + }, ]; tests } @@ -104,7 +253,7 @@ pub fn cmd(quiet: bool) -> Result<()> { Ok(()) } else { Err(anyhow::anyhow!( - "One or more tests in sevctl-ok reported a failure" + "One or more tests in snpguest-ok reported a failure" )) } } @@ -196,90 +345,26 @@ fn emit_skip(tests: &[Test], level: usize, quiet: bool) { } } -fn get_values(reg: u32, cpu: u16) -> Result { +fn get_values(reg: u32, cpu: u16) -> Result { let mut msr = Msr::new(reg, cpu).context("Error Reading MSR")?; - let my_bitfield = BitRead(msr.read()?); + let my_bitfield = SevStatus(msr.read()?); Ok(my_bitfield) } -fn encryption_levels(test: GuestLevels) -> TestResult { - let temp_bitfield = match get_values(0xC0010131, 0) { - Ok(temp_bitfield) => temp_bitfield, - Err(e) => { - return TestResult { - name: test.to_string(), - stat: TestState::Fail, - mesg: Some(format!("Failed to get bit values, {e}")), - } - } - }; +fn run_msr_check(check_bit: u64, sev_feature: &str, optional_field: bool) -> TestResult { + let mut status = TestState::Fail; + let mut message = "DISABLED".to_string(); - match test { - GuestLevels::Sev => { - let sev_status = temp_bitfield.sev_bit(); - if sev_status == 1 { - TestResult { - name: format!("{}", GuestLevels::Sev), - stat: TestState::Pass, - mesg: Some("SEV is ENABLED".to_string()), - } - } else if sev_status == 0 { - TestResult { - name: format!("{}", GuestLevels::Sev), - stat: TestState::Fail, - mesg: Some("SEV is DISABLED".to_string()), - } - } else { - TestResult { - name: format!("{}", GuestLevels::Sev), - stat: TestState::Fail, - mesg: format!("Invalid value found in MSR, {}", sev_status).into(), - } - } - } - GuestLevels::SevEs => { - let sev_es_status = temp_bitfield.es_bit(); - if sev_es_status == 1 { - TestResult { - name: format!("{}", GuestLevels::SevEs), - stat: TestState::Pass, - mesg: Some("SEV-ES is ENABLED".to_string()), - } - } else if sev_es_status == 0 { - TestResult { - name: format!("{}", GuestLevels::SevEs), - stat: TestState::Fail, - mesg: Some("SEV-ES is DISABLED".to_string()), - } - } else { - TestResult { - name: format!("{}", GuestLevels::SevEs), - stat: TestState::Fail, - mesg: format!("Invalid value found in MSR, {}", sev_es_status).into(), - } - } - } - GuestLevels::Snp => { - let snp_status = temp_bitfield.snp_bit(); - if snp_status == 1 { - TestResult { - name: format!("{}", GuestLevels::Snp), - stat: TestState::Pass, - mesg: Some("SNP is ENABLED".to_string()), - } - } else if snp_status == 0 { - TestResult { - name: format!("{}", GuestLevels::Snp), - stat: TestState::Fail, - mesg: Some("SNP is DISABLED".to_string()), - } - } else { - TestResult { - name: format!("{}", GuestLevels::Snp), - stat: TestState::Fail, - mesg: format!("Invalid value found in MSR, {}", snp_status).into(), - } - } - } + if check_bit == 1 { + status = TestState::Pass; + message = "ENABLED".to_string(); + } else if optional_field { + status = TestState::Pass; + } + + TestResult { + name: sev_feature.to_string(), + stat: status, + mesg: Some(message), } }