From 7bc9042a6abb934c473943a9cef95d22c0571aff Mon Sep 17 00:00:00 2001 From: Diego Gonzalez Villalobos Date: Mon, 30 Oct 2023 15:58:08 +0000 Subject: [PATCH] Added state for gctx, fixed comments, separated sev and snp Signed-off-by: Diego Gonzalez Villalobos --- .gitignore | 1 + Cargo.lock | 652 ----------------------- Cargo.toml | 2 +- src/error.rs | 30 +- src/measurement/gctx.rs | 173 +++--- src/measurement/measurement_functions.rs | 227 -------- src/measurement/mod.rs | 7 +- src/measurement/ovmf.rs | 46 +- src/measurement/sev.rs | 115 ++++ src/measurement/sev_hashes.rs | 20 +- src/measurement/snp.rs | 200 +++++++ src/measurement/vcpu_types.rs | 30 +- src/measurement/vmsa.rs | 4 +- tests/measurement.rs | 314 +++++------ 14 files changed, 666 insertions(+), 1155 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 src/measurement/measurement_functions.rs create mode 100644 src/measurement/sev.rs create mode 100644 src/measurement/snp.rs diff --git a/.gitignore b/.gitignore index de358ff3..8be01dad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .vscode/ +Cargo.lock diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index b5bdd145..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,652 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitfield" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - -[[package]] -name = "cc" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "codicon" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12170080f3533d6f09a19f81596f836854d0fa4867dc32c8172b8474b4e9de61" - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "iocuddle" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8972d5be69940353d5347a1344cb375d9b457d6809b428b05bb1ca2fb9ce007" - -[[package]] -name = "kvm-bindings" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe70e65a5b092161d17f5005b66e5eefe7a94a70c332e755036fc4af78c4e79" -dependencies = [ - "vmm-sys-util", -] - -[[package]] -name = "kvm-ioctls" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bdde2b46ee7b6587ef79f751019c4726c4f2d3e4628df5d69f3f9c5cb6c6bd4" -dependencies = [ - "kvm-bindings", - "libc", - "vmm-sys-util", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.148" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" - -[[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "memchr" -version = "2.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "openssl" -version = "0.10.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" -dependencies = [ - "bitflags 2.4.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-sys" -version = "0.9.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.3.5", - "smallvec", - "windows-targets", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "proc-macro2" -version = "1.0.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall 0.2.16", - "thiserror", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.188" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-big-array" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_bytes" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.188" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serial_test" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" -dependencies = [ - "dashmap", - "futures", - "lazy_static", - "log", - "parking_lot", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sev" -version = "1.2.1" -dependencies = [ - "bincode", - "bitfield", - "bitflags 1.3.2", - "codicon", - "dirs", - "hex", - "iocuddle", - "kvm-bindings", - "kvm-ioctls", - "lazy_static", - "libc", - "openssl", - "serde", - "serde-big-array", - "serde_bytes", - "serial_test", - "static_assertions", - "uuid", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "2.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "uuid" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" -dependencies = [ - "serde", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "vmm-sys-util" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48b7b084231214f7427041e4220d77dfe726897a6d41fddee450696e66ff2a29" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml index 96ec1660..a57e8aad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,4 +71,4 @@ byteorder = "1.4.3" [dev-dependencies] kvm-ioctls = ">=0.12" kvm-bindings = ">=0.6" -serial_test = "2.0" +serial_test = "2.0" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index abb4fe2e..df8b75a1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -607,6 +607,12 @@ pub enum GCTXError { /// Block size data was the incorrect size InvalidBlockSize, + /// Missing data to do page update + MissingData, + + /// Missing block size + MissingBlockSize, + /// Unknown Error. UnknownError, } @@ -621,6 +627,12 @@ impl std::fmt::Display for GCTXError { GCTXError::InvalidBlockSize => { write!(f, "Provided data does not conform to a 4096 block size") } + GCTXError::MissingData => { + write!(f, "Did not provide data to perform page update") + } + GCTXError::MissingBlockSize => { + write!(f, "Did not provide block size to perform page update") + } GCTXError::UnknownError => write!(f, "An unknown Guest Context error encountered"), } } @@ -644,7 +656,7 @@ pub enum OVMFError { InvalidSize(String, usize, usize), /// GUID doesn't match expected GUID - MismatchingGUID(), + MismatchingGUID, /// Unknown Error. UnknownError, @@ -663,18 +675,18 @@ impl std::fmt::Display for OVMFError { OVMFError::InvalidSize(entry, actual, expected) => { write!(f, "Invalid size of {entry}: {actual} < {expected}") } - OVMFError::MismatchingGUID() => { + OVMFError::MismatchingGUID => { write!(f, "OVMF table footer GUID does not match expected GUID") } - OVMFError::UnknownError => write!(f, "An unknown Guest Context error encountered"), + OVMFError::UnknownError => write!(f, "An unknown OVMF error encountered"), } } } impl std::error::Error for OVMFError {} -#[derive(Debug)] /// Errors which may be encountered when building SEV hashes. +#[derive(Debug)] pub enum SevHashError { /// Provided page has invalid size InvalidSize(usize, usize), @@ -695,15 +707,15 @@ impl std::fmt::Display for SevHashError { SevHashError::InvalidSize(actual, expected) => { write!(f, "Invalid page Size: {actual} vs {expected}") } - SevHashError::UnknownError => write!(f, "An unknown Guest Context error encountered"), + SevHashError::UnknownError => write!(f, "An unknown SEV Hashing error encountered"), } } } impl std::error::Error for SevHashError {} -#[derive(Debug)] /// Errors which may be encountered when calculating the guest measurement. +#[derive(Debug)] pub enum MeasurementError { /// TryFrom Slice Error handling FromSliceError(TryFromSliceError), @@ -738,8 +750,8 @@ pub enum MeasurementError { /// Invalid SEV Mode provided InvalidSevModeError(String), - /// Kernel specified for wrong OVMF - KernelSpecifiedError, + /// OVMF doesn't support kernel measurement + InvalidOvmfKernelError, /// OVMF is missing required section with kernel specified MissingSection(String), @@ -765,7 +777,7 @@ impl std::fmt::Display for MeasurementError { MeasurementError::InvalidSevModeError(value) => { write!(f, "Invalid SEV mode provided: {value}") } - MeasurementError::KernelSpecifiedError => write!( + MeasurementError::InvalidOvmfKernelError => write!( f, "Kernel specified but OVMF doesn't support kernel/initrd/cmdline measurement" ), diff --git a/src/measurement/gctx.rs b/src/measurement/gctx.rs index 5f19f0aa..4640d26e 100644 --- a/src/measurement/gctx.rs +++ b/src/measurement/gctx.rs @@ -1,54 +1,61 @@ // SPDX-License-Identifier: Apache-2.0 +use std::convert::TryInto; + use openssl::sha::sha384; -use crate::error::*; -use std::convert::TryInto; +use crate::{error::*, launch::snp::PageType}; // Launch digest size in bytes -const LD_SIZE: usize = 384 / 8; +pub(crate) const LD_SIZE: usize = 384 / 8; // VMSA page is recorded in the RMP table with GPA (u64)(-1). // However, the address is page-aligned, and also all the bits above // 51 are cleared. -const VMSA_GPA: u64 = 0xFFFFFFFFF000; +pub(crate) const VMSA_GPA: u64 = 0xFFFFFFFFF000; // Launch digest intialized in all zeros const ZEROS: [u8; LD_SIZE] = [0; LD_SIZE]; +fn validate_block_size(length: usize) -> Result<(), GCTXError> { + if (length % 4096) != 0 { + Err(GCTXError::InvalidBlockSize) + } else { + Ok(()) + } +} + +pub(crate) struct Updating; +pub(crate) struct Completed; + /// Guest context field structure -pub struct Gctx { +pub struct Gctx { /// Launch Digest, 48 bytes long ld: [u8; LD_SIZE], + _state: T, } /// Default init of GCTX, launch digest of all 0s -impl Default for Gctx { - fn default() -> Gctx { - Gctx { ld: ZEROS } +impl Default for Gctx { + fn default() -> Self { + Self { + ld: ZEROS, + _state: Updating, + } } } -impl Gctx { +impl Gctx { /// Initialize a new guest context using existing data pub fn new(seed: &[u8]) -> Result { Ok(Self { ld: seed.try_into()?, + _state: Updating, }) } - /// Get the launch digest bytes - pub fn get_ld(self) -> [u8; LD_SIZE] { - self.ld - } - /// Will update guest context launch digest with provided data from page - fn update( - &mut self, - page_type: u8, - gpa: u64, - contents: [u8; LD_SIZE], - ) -> Result<(), GCTXError> { + fn update(&mut self, page_type: u8, gpa: u64, contents: &[u8]) -> Result<(), GCTXError> { let page_info_len: u16 = 0x70; let is_imi: u8 = 0; let vmpl3_perms: u8 = 0; @@ -56,7 +63,7 @@ impl Gctx { let vmpl1_perms: u8 = 0; let mut page_info: Vec = self.ld.to_vec(); - page_info.extend_from_slice(&contents); + page_info.extend_from_slice(contents); page_info.extend_from_slice(&page_info_len.to_le_bytes()); page_info.extend_from_slice(&page_type.to_le_bytes()); @@ -75,63 +82,93 @@ impl Gctx { page_info_len as usize, )); } - self.ld = sha384(&page_info); Ok(()) } - /// Update launch digest using normal memory pages - pub fn update_normal_pages(&mut self, start_gpa: u64, data: &[u8]) -> Result<(), GCTXError> { - if (data.len() % 4096) != 0 { - return Err(GCTXError::InvalidBlockSize); - } - let mut offset = 0; - while offset < data.len() { - let page_data = &data[offset..offset + 4096]; - self.update(0x01, start_gpa + offset as u64, sha384(page_data))?; - offset += 4096; - } - Ok(()) - } - - /// Update launch digest using VMSA memory pages - pub fn update_vmsa_page(&mut self, data: &[u8]) -> Result<(), GCTXError> { - if data.len() != 4096 { - return Err(GCTXError::InvalidBlockSize); + /// Update Lanunch digest type accprding to page type and guest physical address. + /// Some Page types don't require data. Some page types just require size of the page. + pub fn update_page( + &mut self, + page_type: PageType, + gpa: u64, + contents: Option<&[u8]>, + length_bytes: Option, + ) -> Result<(), GCTXError> { + match page_type { + PageType::Normal => { + if let Some(data) = contents { + validate_block_size(data.len())?; + let mut offset = 0; + while offset < data.len() { + let page_data = &data[offset..offset + 4096]; + self.update( + page_type as u8, + gpa + offset as u64, + sha384(page_data).as_slice(), + )?; + offset += 4096; + } + Ok(()) + } else { + Err(GCTXError::MissingData) + } + } + + PageType::Vmsa => { + if let Some(data) = contents { + validate_block_size(data.len())?; + self.update(page_type as u8, VMSA_GPA, sha384(data).as_slice())?; + Ok(()) + } else { + Err(GCTXError::MissingData) + } + } + + PageType::Zero => { + if let Some(length_bytes) = length_bytes { + validate_block_size(length_bytes)?; + let mut offset = 0; + while offset < length_bytes { + self.update(page_type as u8, gpa + offset as u64, &ZEROS)?; + offset += 4096; + } + Ok(()) + } else { + Err(GCTXError::MissingBlockSize) + } + } + + PageType::Unmeasured => { + self.update(page_type as u8, gpa, &ZEROS)?; + Ok(()) + } + + PageType::Secrets => { + self.update(page_type as u8, gpa, &ZEROS)?; + Ok(()) + } + + PageType::Cpuid => { + self.update(page_type as u8, gpa, &ZEROS)?; + Ok(()) + } } - self.update(0x02, VMSA_GPA, sha384(data))?; - Ok(()) } - /// Update launch digest using ZERO pages - pub fn update_zero_pages(&mut self, gpa: u64, length_bytes: usize) -> Result<(), GCTXError> { - if (length_bytes % 4096) != 0 { - return Err(GCTXError::InvalidBlockSize); - }; - let mut offset = 0; - while offset < length_bytes { - self.update(0x03, gpa + offset as u64, ZEROS)?; - offset += 4096; + /// Update is done and now we switch to a completed state + pub(crate) fn finished(&self) -> Gctx { + Gctx { + ld: self.ld, + _state: Completed, } - Ok(()) - } - - /// Update launch digest using an unmeasured page - fn _update_unmeasured_page(&mut self, gpa: u64) -> Result<(), GCTXError> { - self.update(0x04, gpa, ZEROS)?; - Ok(()) - } - - /// Update launch digest using a secret page - pub fn update_secrets_page(&mut self, gpa: u64) -> Result<(), GCTXError> { - self.update(0x05, gpa, ZEROS)?; - Ok(()) } +} - /// Update launch digest using a CPUID page - pub fn update_cpuid_page(&mut self, gpa: u64) -> Result<(), GCTXError> { - self.update(0x06, gpa, ZEROS)?; - Ok(()) +impl Gctx { + /// Get the launch digest bytes + pub(crate) fn ld(&self) -> &[u8; LD_SIZE] { + &self.ld } } diff --git a/src/measurement/measurement_functions.rs b/src/measurement/measurement_functions.rs deleted file mode 100644 index b1e39072..00000000 --- a/src/measurement/measurement_functions.rs +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -//! Operations to calculate guest measurement for different SEV modes -use crate::measurement::{ - gctx::Gctx, - ovmf::{OvmfSevMetadataSectionDesc, SectionType, OVMF}, - sev_hashes::SevHashes, - vcpu_types::CpuType, - vmsa::{SevMode, VMMType, VMSA}, -}; -use hex::FromHex; -use std::path::PathBuf; -use std::str::FromStr; - -use crate::error::*; - -use openssl::sha::Sha256; - -const _PAGE_MASK: u64 = 0xfff; - -/// Get the launch digest as a hex string -pub fn get_hex_ld(ld: Vec) -> String { - hex::encode(ld) -} - -/// Update launch digest with SEV kernel hashes -fn snp_update_kernel_hashes( - gctx: &mut Gctx, - ovmf: &OVMF, - sev_hashes: Option<&SevHashes>, - gpa: u64, - size: usize, -) -> Result<(), MeasurementError> { - match sev_hashes { - Some(hash) => { - let sev_hashes_table_gpa = ovmf.sev_hashes_table_gpa()?; - let page_offset = sev_hashes_table_gpa & _PAGE_MASK; - let sev_hashes_page = hash.construct_page(page_offset as usize)?; - assert_eq!(sev_hashes_page.len(), size); - gctx.update_normal_pages(gpa, sev_hashes_page.as_slice())? - } - None => gctx.update_zero_pages(gpa, size)?, - } - - Ok(()) -} - -/// Update launch digest with different section types -fn snp_update_section( - desc: &OvmfSevMetadataSectionDesc, - gctx: &mut Gctx, - ovmf: &OVMF, - sev_hashes: Option<&SevHashes>, - vmm_type: VMMType, -) -> Result<(), MeasurementError> { - match desc.section_type { - SectionType::SnpSecMemory => gctx.update_zero_pages(desc.gpa.into(), desc.size as usize)?, - SectionType::SnpSecrets => gctx.update_secrets_page(desc.gpa.into())?, - SectionType::CPUID => { - if vmm_type != VMMType::EC2 { - gctx.update_cpuid_page(desc.gpa.into())? - } - } - SectionType::SnpKernelHashes => { - snp_update_kernel_hashes(gctx, ovmf, sev_hashes, desc.gpa.into(), desc.size as usize)? - } - } - - Ok(()) -} - -/// Update GCTX with different metadata pages -fn snp_update_metadata_pages( - gctx: &mut Gctx, - ovmf: &OVMF, - sev_hashes: Option<&SevHashes>, - vmm_type: VMMType, -) -> Result<(), MeasurementError> { - for desc in ovmf.metadata_items().iter() { - snp_update_section(desc, gctx, ovmf, sev_hashes, vmm_type)? - } - - if vmm_type == VMMType::EC2 { - for desc in ovmf.metadata_items() { - if desc.section_type == SectionType::CPUID { - gctx.update_cpuid_page(desc.gpa.into())? - } - } - } - - if sev_hashes.is_some() && !ovmf.has_metadata_section(SectionType::SnpKernelHashes) { - return Err(MeasurementError::MissingSection( - "SNP_KERNEL_HASHES".to_string(), - )); - } - - Ok(()) -} - -/// Calculate the OVMF hash from OVMF file -pub fn calc_snp_ovmf_hash(ovmf_file: PathBuf) -> Result<[u8; 48], MeasurementError> { - let ovmf = OVMF::new(ovmf_file)?; - let mut gctx = Gctx::default(); - - gctx.update_normal_pages(ovmf.gpa(), ovmf.data())?; - - Ok(gctx.get_ld()) -} - -/// Calulate an SEV-SNP launch digest -#[allow(clippy::too_many_arguments)] -pub fn snp_calc_launch_digest( - vcpus: u32, - vcpu_type: String, - ovmf_file: PathBuf, - kernel_file: Option, - initrd_file: Option, - append: Option<&str>, - ovmf_hash_str: Option<&str>, - vmm_type: Option, -) -> Result<[u8; 48], MeasurementError> { - let ovmf = OVMF::new(ovmf_file)?; - - let mut gctx = match ovmf_hash_str { - Some(hash) => { - let ovmf_hash = Vec::from_hex(hash)?; - Gctx::new(ovmf_hash.as_slice()) - } - None => { - let mut gctx = Gctx::default(); - - gctx.update_normal_pages(ovmf.gpa(), ovmf.data())?; - - Ok(gctx) - } - }?; - - let sev_hashes = match kernel_file { - Some(kernel) => Some(SevHashes::new(kernel, initrd_file, append)?), - None => None, - }; - - let official_vmm_type = match vmm_type { - Some(vmm) => vmm, - None => VMMType::QEMU, - }; - - snp_update_metadata_pages(&mut gctx, &ovmf, sev_hashes.as_ref(), official_vmm_type)?; - - let vmsa = VMSA::new( - SevMode::SevSnp, - ovmf.sev_es_reset_eip()?.into(), - CpuType::from_str(vcpu_type.as_str())?, - official_vmm_type, - Some(vcpus as u64), - ); - - for vmsa_page in vmsa.pages(vcpus as usize)?.iter() { - gctx.update_vmsa_page(vmsa_page.as_slice())? - } - - Ok(gctx.get_ld()) -} - -/// Calculate an SEV-ES launch digest -pub fn seves_calc_launch_digest( - vcpus: u32, - vcpu_type: String, - ovmf_file: PathBuf, - kernel_file: Option, - initrd_file: Option, - append: Option<&str>, - vmm_type: Option, -) -> Result<[u8; 32], MeasurementError> { - let ovmf = OVMF::new(ovmf_file)?; - let mut launch_hash = Sha256::new(); - launch_hash.update(ovmf.data().as_slice()); - - if let Some(kernel) = kernel_file { - if !ovmf.is_sev_hashes_table_supported() { - return Err(MeasurementError::KernelSpecifiedError); - } - let sev_hashes = SevHashes::new(kernel, initrd_file, append)?.construct_table()?; - launch_hash.update(sev_hashes.as_slice()); - }; - - let official_vmm_type = match vmm_type { - Some(vmm) => vmm, - None => VMMType::QEMU, - }; - - let vmsa = VMSA::new( - SevMode::SevEs, - ovmf.sev_es_reset_eip()?.into(), - CpuType::from_str(vcpu_type.as_str())?, - official_vmm_type, - Some(vcpus as u64), - ); - - for vmsa_page in vmsa.pages(vcpus as usize)?.iter() { - launch_hash.update(vmsa_page.as_slice()) - } - - Ok(launch_hash.finish()) -} - -/// Calculate an SEV launch digest -pub fn sev_calc_launch_digest( - ovmf_file: PathBuf, - kernel_file: Option, - initrd_file: Option, - append: Option<&str>, -) -> Result<[u8; 32], MeasurementError> { - let ovmf = OVMF::new(ovmf_file)?; - let mut launch_hash = Sha256::new(); - launch_hash.update(ovmf.data().as_slice()); - - if let Some(kernel) = kernel_file { - if !ovmf.is_sev_hashes_table_supported() { - return Err(MeasurementError::KernelSpecifiedError); - } - let sev_hashes = SevHashes::new(kernel, initrd_file, append)?.construct_table()?; - launch_hash.update(sev_hashes.as_slice()); - }; - - Ok(launch_hash.finish()) -} diff --git a/src/measurement/mod.rs b/src/measurement/mod.rs index 14f783ca..1b80309e 100644 --- a/src/measurement/mod.rs +++ b/src/measurement/mod.rs @@ -21,5 +21,8 @@ pub mod sev_hashes; #[cfg(any(feature = "sev", feature = "snp"))] pub mod vcpu_types; -#[cfg(all(any(feature = "sev", feature = "snp"), feature = "openssl"))] -pub mod measurement_functions; +#[cfg(all(feature = "snp", feature = "openssl"))] +pub mod snp; + +#[cfg(all(feature = "sev", feature = "openssl"))] +pub mod sev; diff --git a/src/measurement/ovmf.rs b/src/measurement/ovmf.rs index 4ed6cd50..cbd24517 100644 --- a/src/measurement/ovmf.rs +++ b/src/measurement/ovmf.rs @@ -56,6 +56,15 @@ impl TryFrom for SectionType { } } } +/// Creating strucutre from bytes +pub trait TryFromBytes { + /// Error when attempting to deserialize from_bytes + type Error; + /// Creating structure from bytes function + fn try_from_bytes(value: &[u8], offset: usize) -> Result + where + Self: std::marker::Sized; +} /// OVMF SEV Metadata Section Description #[repr(C)] @@ -69,9 +78,9 @@ pub struct OvmfSevMetadataSectionDesc { pub section_type: SectionType, } -impl OvmfSevMetadataSectionDesc { - /// Generate Description from bytes - fn from_bytes(value: &[u8], offset: usize) -> Result { +impl TryFromBytes for OvmfSevMetadataSectionDesc { + type Error = MeasurementError; + fn try_from_bytes(value: &[u8], offset: usize) -> Result { let value = &value[offset..offset + std::mem::size_of::()]; bincode::deserialize(value).map_err(|e| MeasurementError::BincodeError(*e)) } @@ -91,13 +100,15 @@ struct OvmfSevMetadataHeader { num_items: u32, } -impl OvmfSevMetadataHeader { - /// Generate Header from bytes - fn from_bytes(value: &[u8], offset: usize) -> Result { +impl TryFromBytes for OvmfSevMetadataHeader { + type Error = MeasurementError; + fn try_from_bytes(value: &[u8], offset: usize) -> Result { let value = &value[offset..offset + std::mem::size_of::()]; bincode::deserialize(value).map_err(|e| MeasurementError::BincodeError(*e)) } +} +impl OvmfSevMetadataHeader { /// Verify Header Signature fn verify(&self) -> Result<(), OVMFError> { let expected_signature: &[u8] = b"ASEV"; @@ -251,15 +262,16 @@ impl OVMF { let expected_footer_guid = guid_le_to_slice(OVMF_TABLE_FOOTER_GUID.to_string().as_str())?; if !footer.guid.eq(&expected_footer_guid) { - return Err(MeasurementError::OVMFError(OVMFError::MismatchingGUID())); + return Err(OVMFError::MismatchingGUID.into()); } if (footer.size as usize) < ENTRY_HEADER_SIZE { - return Err(MeasurementError::OVMFError(OVMFError::InvalidSize( + return Err(OVMFError::InvalidSize( "OVMF Table Footer".to_string(), footer.size as usize, ENTRY_HEADER_SIZE, - ))); + ) + .into()); } let table_size = footer.size as usize - ENTRY_HEADER_SIZE; @@ -271,11 +283,12 @@ impl OVMF { let entry = OvmfFooterTableEntry::try_from(&table_bytes[offset - ENTRY_HEADER_SIZE..offset])?; if entry.size < ENTRY_HEADER_SIZE as u16 { - return Err(MeasurementError::OVMFError(OVMFError::InvalidSize( + return Err(OVMFError::InvalidSize( "OVMF Table Entry".to_string(), entry.size as usize, ENTRY_HEADER_SIZE, - ))); + ) + .into()); } let entry_guid = Uuid::from_slice_le(&entry.guid)?; @@ -297,21 +310,22 @@ impl OVMF { Some(entry) => { let offset_from_end = i32::from_le_bytes(entry[..4].try_into()?); let header_start = self.data.len() - (offset_from_end as usize); - let header = OvmfSevMetadataHeader::from_bytes(self.data.as_slice(), header_start)?; + let header = + OvmfSevMetadataHeader::try_from_bytes(self.data.as_slice(), header_start)?; header.verify()?; let items = &self.data[header_start + std::mem::size_of::() ..header_start + header.size as usize]; for i in 0..header.num_items { let offset = (i as usize) * std::mem::size_of::(); - let item = OvmfSevMetadataSectionDesc::from_bytes(items, offset)?; + let item = OvmfSevMetadataSectionDesc::try_from_bytes(items, offset)?; self.metadata_items.push(item.to_owned()); } } None => { - return Err(MeasurementError::OVMFError(OVMFError::EntryMissingInTable( - "OVMF_SEV_METADATA_GUID".to_string(), - ))); + return Err( + OVMFError::EntryMissingInTable("OVMF_SEV_METADATA_GUID".to_string()).into(), + ); } } diff --git a/src/measurement/sev.rs b/src/measurement/sev.rs new file mode 100644 index 00000000..6358da3e --- /dev/null +++ b/src/measurement/sev.rs @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Operations to calculate guest measurement for different SEV modes +use crate::measurement::{ + ovmf::OVMF, + sev_hashes::SevHashes, + vcpu_types::CpuType, + vmsa::{SevMode, VMMType, VMSA}, +}; + +use std::path::PathBuf; +use std::str::FromStr; + +use crate::error::*; + +use openssl::sha::Sha256; + +const _PAGE_MASK: u64 = 0xfff; + +/// Get the launch digest as a hex string +pub fn get_hex_ld(ld: Vec) -> String { + hex::encode(ld) +} + +/// Arguments required to calculate the SEV-ES measurement +pub struct SevEsMeasurementArgs<'a> { + /// Number of vcpus + pub vcpus: u32, + /// vcpu type + pub vcpu_type: String, + /// Path to OVMF file + pub ovmf_file: PathBuf, + /// Path to kernel file + pub kernel_file: Option, + /// Path to initrd file + pub initrd_file: Option, + /// Append arguments for kernel + pub append: Option<&'a str>, + /// vmm type + pub vmm_type: Option, +} + +/// Calculate an SEV-ES launch digest +pub fn seves_calc_launch_digest( + sev_es_measurement: SevEsMeasurementArgs, +) -> Result<[u8; 32], MeasurementError> { + let ovmf = OVMF::new(sev_es_measurement.ovmf_file)?; + let mut launch_hash = Sha256::new(); + launch_hash.update(ovmf.data().as_slice()); + + if let Some(kernel) = sev_es_measurement.kernel_file { + if !ovmf.is_sev_hashes_table_supported() { + return Err(MeasurementError::InvalidOvmfKernelError); + } + let sev_hashes = SevHashes::new( + kernel, + sev_es_measurement.initrd_file, + sev_es_measurement.append, + )? + .construct_table()?; + launch_hash.update(sev_hashes.as_slice()); + }; + + let official_vmm_type = match sev_es_measurement.vmm_type { + Some(vmm) => vmm, + None => VMMType::QEMU, + }; + + let vmsa = VMSA::new( + SevMode::SevEs, + ovmf.sev_es_reset_eip()?.into(), + CpuType::from_str(sev_es_measurement.vcpu_type.as_str())?, + official_vmm_type, + Some(sev_es_measurement.vcpus as u64), + ); + + for vmsa_page in vmsa.pages(sev_es_measurement.vcpus as usize)?.iter() { + launch_hash.update(vmsa_page.as_slice()) + } + + Ok(launch_hash.finish()) +} + +/// Arguments required to calculate the SEV measurement +pub struct SevMeasurementArgs<'a> { + /// Path to OVMF file + pub ovmf_file: PathBuf, + /// Path to kernel file + pub kernel_file: Option, + /// Path to initrd file + pub initrd_file: Option, + /// Append arguments for kernel + pub append: Option<&'a str>, +} + +/// Calculate an SEV launch digest +pub fn sev_calc_launch_digest( + sev_measurement: SevMeasurementArgs, +) -> Result<[u8; 32], MeasurementError> { + let ovmf = OVMF::new(sev_measurement.ovmf_file)?; + let mut launch_hash = Sha256::new(); + launch_hash.update(ovmf.data().as_slice()); + + if let Some(kernel) = sev_measurement.kernel_file { + if !ovmf.is_sev_hashes_table_supported() { + return Err(MeasurementError::InvalidOvmfKernelError); + } + let sev_hashes = + SevHashes::new(kernel, sev_measurement.initrd_file, sev_measurement.append)? + .construct_table()?; + launch_hash.update(sev_hashes.as_slice()); + }; + + Ok(launch_hash.finish()) +} diff --git a/src/measurement/sev_hashes.rs b/src/measurement/sev_hashes.rs index 26219365..d77876ba 100644 --- a/src/measurement/sev_hashes.rs +++ b/src/measurement/sev_hashes.rs @@ -24,9 +24,11 @@ struct GuidLe { _data: [u8; 16], } -impl GuidLe { - fn from_uuid(guid: &Uuid) -> Result { - let guid = guid.to_bytes_le(); +impl TryFrom<&Uuid> for GuidLe { + type Error = MeasurementError; + + fn try_from(value: &Uuid) -> Result { + let guid = value.to_bytes_le(); let guid = guid.as_slice(); Ok(Self { _data: guid.try_into()?, @@ -62,7 +64,7 @@ struct SevHashTableEntry { impl SevHashTableEntry { fn new(guid: &Uuid, hash: Sha256Hash) -> Result { Ok(Self { - guid: GuidLe::from_uuid(guid)?, + guid: GuidLe::try_from(guid)?, length: std::mem::size_of::() as u16, hash, }) @@ -149,7 +151,6 @@ impl SevHashes { kernel_file.read_to_end(&mut kernel_data)?; let kernel_hash = sha256(&kernel_data); - let initrd_data = match initrd { Some(path) => { let mut initrd_file = File::open(path)?; @@ -197,9 +198,7 @@ impl SevHashes { /// Construct an SEV Hash page using hash table. pub fn construct_page(&self, offset: usize) -> Result, MeasurementError> { if offset >= 4096 { - return Err(MeasurementError::SevHashError(SevHashError::InvalidOffset( - offset, 4096, - ))); + return Err(SevHashError::InvalidOffset(offset, 4096).into()); } let hashes_table = self.construct_table()?; @@ -208,10 +207,7 @@ impl SevHashes { page.extend_from_slice(&hashes_table[..]); page.resize(4096, 0); if page.len() != 4096 { - return Err(MeasurementError::SevHashError(SevHashError::InvalidSize( - page.len(), - 4096, - ))); + return Err(SevHashError::InvalidSize(page.len(), 4096).into()); } Ok(page) } diff --git a/src/measurement/snp.rs b/src/measurement/snp.rs new file mode 100644 index 00000000..2d5f539c --- /dev/null +++ b/src/measurement/snp.rs @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Operations to calculate guest measurement for different SEV modes +use crate::{ + launch::snp::PageType, + measurement::{ + gctx::{Gctx, Updating, VMSA_GPA}, + ovmf::{OvmfSevMetadataSectionDesc, SectionType, OVMF}, + sev_hashes::SevHashes, + vcpu_types::CpuType, + vmsa::{SevMode, VMMType, VMSA}, + }, +}; +use hex::FromHex; +use std::path::PathBuf; +use std::str::FromStr; + +use crate::error::*; + +use super::gctx::LD_SIZE; + +const _PAGE_MASK: u64 = 0xfff; + +/// Get the launch digest as a hex string +pub fn get_hex_ld(ld: Vec) -> String { + hex::encode(ld) +} + +/// Update launch digest with SEV kernel hashes +fn snp_update_kernel_hashes( + gctx: &mut Gctx, + ovmf: &OVMF, + sev_hashes: Option<&SevHashes>, + gpa: u64, + size: usize, +) -> Result<(), MeasurementError> { + match sev_hashes { + Some(hash) => { + let sev_hashes_table_gpa = ovmf.sev_hashes_table_gpa()?; + let page_offset = sev_hashes_table_gpa & _PAGE_MASK; + let sev_hashes_page = hash.construct_page(page_offset as usize)?; + assert_eq!(sev_hashes_page.len(), size); + gctx.update_page( + PageType::Normal, + gpa, + Some(sev_hashes_page.as_slice()), + None, + )? + } + None => gctx.update_page(PageType::Zero, gpa, None, Some(size))?, + } + + Ok(()) +} + +/// Update launch digest with different section types +fn snp_update_section( + desc: &OvmfSevMetadataSectionDesc, + gctx: &mut Gctx, + ovmf: &OVMF, + sev_hashes: Option<&SevHashes>, + vmm_type: VMMType, +) -> Result<(), MeasurementError> { + match desc.section_type { + SectionType::SnpSecMemory => gctx.update_page( + PageType::Zero, + desc.gpa.into(), + None, + Some(desc.size as usize), + )?, + SectionType::SnpSecrets => { + gctx.update_page(PageType::Secrets, desc.gpa.into(), None, None)? + } + SectionType::CPUID => { + if vmm_type != VMMType::EC2 { + gctx.update_page(PageType::Cpuid, desc.gpa.into(), None, None)? + } + } + SectionType::SnpKernelHashes => { + snp_update_kernel_hashes(gctx, ovmf, sev_hashes, desc.gpa.into(), desc.size as usize)? + } + } + + Ok(()) +} + +/// Update GCTX with different metadata pages +fn snp_update_metadata_pages( + gctx: &mut Gctx, + ovmf: &OVMF, + sev_hashes: Option<&SevHashes>, + vmm_type: VMMType, +) -> Result<(), MeasurementError> { + for desc in ovmf.metadata_items().iter() { + snp_update_section(desc, gctx, ovmf, sev_hashes, vmm_type)?; + // if vmm_type == VMMType::EC2 && desc.section_type == SectionType::CPUID { + // gctx.update_cpuid_page(desc.gpa.into())?; + // } + } + + if vmm_type == VMMType::EC2 { + for desc in ovmf.metadata_items() { + if desc.section_type == SectionType::CPUID { + gctx.update_page(PageType::Cpuid, desc.gpa.into(), None, None)? + } + } + } + if sev_hashes.is_some() && !ovmf.has_metadata_section(SectionType::SnpKernelHashes) { + return Err(MeasurementError::MissingSection( + "SNP_KERNEL_HASHES".to_string(), + )); + }; + + Ok(()) +} + +/// Calculate the OVMF hash from OVMF file +pub fn calc_snp_ovmf_hash(ovmf_file: PathBuf) -> Result<[u8; LD_SIZE], MeasurementError> { + let ovmf = OVMF::new(ovmf_file)?; + let mut gctx = Gctx::default(); + + gctx.update_page(PageType::Normal, ovmf.gpa(), Some(ovmf.data()), None)?; + + let gctx = gctx.finished(); + + Ok(*gctx.ld()) +} + +/// Arguments required to calculate the SNP measurement +pub struct SnpMeasurementArgs<'a> { + /// Number of vcpus + pub vcpus: u32, + /// vcpu type + pub vcpu_type: String, + /// Path to OVMF file + pub ovmf_file: PathBuf, + /// Path to kernel file + pub kernel_file: Option, + /// Path to initrd file + pub initrd_file: Option, + /// Append arguments for kernel + pub append: Option<&'a str>, + /// Already calculated ovmf hash + pub ovmf_hash_str: Option<&'a str>, + /// vmm type + pub vmm_type: Option, +} + +/// Calulate an SEV-SNP launch digest +pub fn snp_calc_launch_digest( + snp_measurement: SnpMeasurementArgs, +) -> Result<[u8; LD_SIZE], MeasurementError> { + let ovmf = OVMF::new(snp_measurement.ovmf_file)?; + + let mut gctx: Gctx = match snp_measurement.ovmf_hash_str { + Some(hash) => { + let ovmf_hash = Vec::from_hex(hash)?; + Gctx::new(ovmf_hash.as_slice())? + } + None => { + let mut gctx = Gctx::default(); + + gctx.update_page(PageType::Normal, ovmf.gpa(), Some(ovmf.data()), None)?; + + gctx + } + }; + + let sev_hashes = match snp_measurement.kernel_file { + Some(kernel) => Some(SevHashes::new( + kernel, + snp_measurement.initrd_file, + snp_measurement.append, + )?), + None => None, + }; + + let official_vmm_type = match snp_measurement.vmm_type { + Some(vmm) => vmm, + None => VMMType::QEMU, + }; + + snp_update_metadata_pages(&mut gctx, &ovmf, sev_hashes.as_ref(), official_vmm_type)?; + + let vmsa = VMSA::new( + SevMode::SevSnp, + ovmf.sev_es_reset_eip()?.into(), + CpuType::from_str(snp_measurement.vcpu_type.as_str())?, + official_vmm_type, + Some(snp_measurement.vcpus as u64), + ); + + for vmsa_page in vmsa.pages(snp_measurement.vcpus as usize)?.iter() { + gctx.update_page(PageType::Vmsa, VMSA_GPA, Some(vmsa_page.as_slice()), None)? + } + + let gctx = gctx.finished(); + + Ok(*gctx.ld()) +} diff --git a/src/measurement/vcpu_types.rs b/src/measurement/vcpu_types.rs index bc0d08bb..db7c063f 100644 --- a/src/measurement/vcpu_types.rs +++ b/src/measurement/vcpu_types.rs @@ -9,35 +9,35 @@ use crate::error::MeasurementError; #[derive(Debug, Clone, Copy, PartialEq)] pub enum CpuType { /// EPYC - Epyc = 0, + Epyc, /// EPYC V1 - EpycV1 = 1, + EpycV1, /// EPYC V2 - EpycV2 = 2, + EpycV2, /// EPYC Indirect Branch Predictor Barrier - EpycIBPB = 3, + EpycIBPB, /// EPYC V3 - EpycV3 = 4, + EpycV3, /// EPYC V4 - EpycV4 = 5, + EpycV4, /// EPYC ROME - EpycRome = 6, + EpycRome, /// EPYC ROME V1 - EpycRomeV1 = 7, + EpycRomeV1, ///EPYC ROME V2 - EpycRomeV2 = 8, + EpycRomeV2, /// EPYC ROME V3 - EpycRomeV3 = 9, + EpycRomeV3, /// EPYC MILAN - EpycMilan = 10, + EpycMilan, /// EPYC MILAN V1 - EpycMilanV1 = 11, + EpycMilanV1, /// EPYC MILAN V2 - EpycMilanV2 = 12, + EpycMilanV2, /// EPYC GENOA - EpycGenoa = 13, + EpycGenoa, /// EPYC GENOA V1 - EpycGenoaV1 = 14, + EpycGenoaV1, } impl TryFrom for CpuType { diff --git a/src/measurement/vmsa.rs b/src/measurement/vmsa.rs index be140d58..0f6c95df 100644 --- a/src/measurement/vmsa.rs +++ b/src/measurement/vmsa.rs @@ -70,7 +70,7 @@ impl fmt::Debug for VMMType { /// The layout of a VMCB struct is documented in Table B-1 of the /// AMD64 Architecture Programmer’s Manual, Volume 2: System Programming #[repr(C)] -#[derive(Debug, Clone, Copy, Serialize, Default)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] struct VmcbSeg { /// Segment selector: documented in Figure 4-3 of the /// AMD64 Architecture Programmer’s Manual, Volume 2: System Programming @@ -119,7 +119,7 @@ where /// https://github.com/AMDESE/linux/blob/sev-snp-v12/arch/x86/include/asm/svm.h#L318 /// (following the definitions in AMD APM Vol 2 Table B-4) #[repr(C)] -#[derive(Debug, Clone, Copy, Serialize, Default)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] struct SevEsSaveArea { es: VmcbSeg, cs: VmcbSeg, diff --git a/tests/measurement.rs b/tests/measurement.rs index 4f860bb2..0afd9594 100644 --- a/tests/measurement.rs +++ b/tests/measurement.rs @@ -4,23 +4,24 @@ #[cfg(feature = "snp")] mod snp_tests { - use sev::measurement::{measurement_functions::*, vmsa::VMMType}; + use sev::measurement::{snp::*, vmsa::VMMType}; // Test of we can generate a good OVMF hash #[test] fn test_snp_ovmf_hash_gen() { let ovmf_hash = "cab7e085874b3acfdbe2d96dcaa3125111f00c35c6fc9708464c2ae74bfdb048a198cb9a9ccae0b3e5e1a33f5f249819"; - let ld = snp_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - None, - Some(ovmf_hash), - Some(VMMType::QEMU), - ) - .unwrap(); + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + ovmf_hash_str: Some(ovmf_hash), + vmm_type: Some(VMMType::QEMU), + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -40,17 +41,18 @@ mod snp_tests { assert_eq!(ovmf_hash.as_str(), exp_hash); - let ld = snp_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - Some("console=ttyS0 loglevel=7"), - Some(ovmf_hash.as_str()), - None, - ) - .unwrap(); + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: Some("console=ttyS0 loglevel=7"), + ovmf_hash_str: Some(ovmf_hash.as_str()), + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -62,17 +64,18 @@ mod snp_tests { // Test EC2 vmm type #[test] fn test_snp_ec2() { - let ld = snp_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - None, - None, - Some(VMMType::EC2), - ) - .unwrap(); + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + ovmf_hash_str: None, + vmm_type: Some(VMMType::EC2), + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -84,17 +87,18 @@ mod snp_tests { // Test a regular snp type #[test] fn test_snp() { - let ld = snp_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - Some("console=ttyS0 loglevel=7"), - None, - None, - ) - .unwrap(); + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: Some("console=ttyS0 loglevel=7"), + ovmf_hash_str: None, + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -106,17 +110,18 @@ mod snp_tests { // Test a regular snp without specified kernel #[test] fn test_snp_without_kernel() { - let ld = snp_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - None, - None, - None, - None, - None, - ) - .unwrap(); + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: None, + initrd_file: None, + append: None, + ovmf_hash_str: None, + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -128,17 +133,18 @@ mod snp_tests { // Test snp with multiple cpus #[test] fn test_snp_with_multiple_vcpus() { - let ld = snp_calc_launch_digest( - 4, - "EPYC-v4".into(), - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - None, - None, - None, - ) - .unwrap(); + let arguments = SnpMeasurementArgs { + vcpus: 4, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + ovmf_hash_str: None, + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -150,17 +156,18 @@ mod snp_tests { // Test snp with with ovmf64 and no kernel #[test] fn test_snp_with_ovmfx64_without_kernel() { - let ld = snp_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), - None, - None, - None, - None, - None, - ) - .unwrap(); + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), + kernel_file: None, + initrd_file: None, + append: None, + ovmf_hash_str: None, + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -175,40 +182,41 @@ mod snp_tests { expected = "Kernel specified but OVMF metadata doesn't include SNP_KERNEL_HASHES section" )] fn test_snp_with_ovmfx64_and_kernel_should_fail() { + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + ovmf_hash_str: None, + vmm_type: None, + }; + panic!( "{}", - snp_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - None, - None, - None - ) - .unwrap_err() - .to_string() + snp_calc_launch_digest(arguments).unwrap_err().to_string() ); } } #[cfg(feature = "sev")] mod sev_tests { - use sev::measurement::measurement_functions::*; + use sev::measurement::sev::*; // test regular sev-es #[test] fn test_seves() { - let ld = seves_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - None, - None, - ) - .unwrap(); + let arguments = SevEsMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + vmm_type: None, + }; + + let ld = seves_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -220,16 +228,17 @@ mod sev_tests { // test sev-es with multiple vcpus #[test] fn test_seves_with_multiple_vcpus() { - let ld = seves_calc_launch_digest( - 4, - "EPYC-v4".into(), - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - None, - None, - ) - .unwrap(); + let arguments = SevEsMeasurementArgs { + vcpus: 4, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + vmm_type: None, + }; + + let ld = seves_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -244,32 +253,33 @@ mod sev_tests { expected = "Kernel specified but OVMF doesn't support kernel/initrd/cmdline measurement" )] fn test_seves_with_ovmfx64_and_kernel_should_fail() { + let arguments = SevEsMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + vmm_type: None, + }; + panic!( "{}", - seves_calc_launch_digest( - 1, - "EPYC-v4".into(), - "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - None, - None - ) - .unwrap_err() - .to_string() + seves_calc_launch_digest(arguments).unwrap_err().to_string() ); } // test regular sev #[test] fn test_sev() { - let ld = sev_calc_launch_digest( - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - Some("console=ttyS0 loglevel=7"), - ) - .unwrap(); + let arguments = SevMeasurementArgs { + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: Some("console=ttyS0 loglevel=7"), + }; + + let ld = sev_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -281,13 +291,14 @@ mod sev_tests { // test sev kernel with no initrd or append #[test] fn test_sev_with_kernel_without_initrd_and_append() { - let ld = sev_calc_launch_digest( - "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), - Some("/dev/null".into()), - None, - None, - ) - .unwrap(); + let arguments = SevMeasurementArgs { + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: None, + append: None, + }; + + let ld = sev_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -299,13 +310,14 @@ mod sev_tests { // test sev with ovmfx64 #[test] fn test_sev_with_ovmfx64_without_kernel() { - let ld = sev_calc_launch_digest( - "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), - None, - None, - None, - ) - .unwrap(); + let arguments = SevMeasurementArgs { + ovmf_file: "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), + kernel_file: None, + initrd_file: None, + append: None, + }; + + let ld = sev_calc_launch_digest(arguments).unwrap(); let ld_hex = hex::encode(ld); @@ -320,16 +332,16 @@ mod sev_tests { expected = "Kernel specified but OVMF doesn't support kernel/initrd/cmdline measurement" )] fn test_sev_with_ovmfx64_and_kernel_should_fail() { + let arguments = SevMeasurementArgs { + ovmf_file: "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + }; + panic!( "{}", - sev_calc_launch_digest( - "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), - Some("/dev/null".into()), - Some("/dev/null".into()), - None - ) - .unwrap_err() - .to_string() + sev_calc_launch_digest(arguments).unwrap_err().to_string() ); } }