├── .cargo └── config.toml ├── .config └── nextest.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── igvm ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── c_api.rs │ ├── hv_defs.rs │ ├── lib.rs │ ├── page_table.rs │ ├── registers.rs │ └── snp_defs.rs ├── igvm_c ├── Makefile ├── README.md ├── cbindgen_igvm.toml ├── cbindgen_igvm_defs.toml ├── igvm.pc.in ├── sample │ └── dump_igvm.c ├── scripts │ └── post_process.sh ├── test_data │ ├── Cargo.toml │ └── src │ │ └── main.rs └── tests │ └── igvm_test.c └── igvm_defs ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── dt.rs └── lib.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # Specify a common set of lints using rustflags for all targets. 5 | [target.'cfg(all())'] 6 | rustflags = [ 7 | # Disable FPO to get full call stack as the '--call-graph=dwarf' option 8 | # is not working well. 9 | "-Cforce-frame-pointers=yes", 10 | 11 | # Enable v0 symbols to get better output from `perf` and related tools. 12 | "-Csymbol-mangling-version=v0", 13 | 14 | # ============ 15 | # Clippy Lints 16 | # ============ 17 | 18 | "-Dfuture_incompatible", 19 | "-Dnonstandard_style", 20 | "-Drust_2018_idioms", 21 | 22 | "-Wunsafe_op_in_unsafe_fn", 23 | "-Wclippy::await_holding_lock", 24 | "-Wclippy::dbg_macro", 25 | "-Wclippy::debug_assert_with_mut_call", 26 | "-Wclippy::filter_map_next", 27 | "-Wclippy::fn_params_excessive_bools", 28 | "-Wclippy::imprecise_flops", 29 | "-Wclippy::inefficient_to_string", 30 | "-Wclippy::linkedlist", 31 | "-Wclippy::lossy_float_literal", 32 | "-Wclippy::macro_use_imports", 33 | "-Wclippy::match_on_vec_items", 34 | "-Wclippy::needless_continue", 35 | "-Wclippy::option_option", 36 | "-Wclippy::ref_option_ref", 37 | "-Wclippy::rest_pat_in_fully_bound_structs", 38 | "-Wclippy::string_to_string", 39 | "-Wclippy::suboptimal_flops", 40 | "-Wclippy::missing_safety_doc", 41 | "-Wclippy::undocumented_unsafe_blocks", 42 | 43 | # Possible future additions: 44 | # 45 | # useful, but _very_ tedious to fix 46 | # "-Wclippy::doc_markdown", 47 | # will be useful to weed-out stubbed codepaths in production 48 | # "-Wclippy::todo", 49 | # "-Wclippy::unimplemented", 50 | # useful, but will require a follow-up PR to enable 51 | # "-Wclippy::map_err_ignore", 52 | # useful, but requires a follow-up PR to enable 53 | # "-Wclippy::unused_self", 54 | 55 | # Nested if / else if statements can be easier to read than an equivalent 56 | # flattened statements. 57 | "-Aclippy::collapsible_else_if", 58 | "-Aclippy::collapsible_if", 59 | # There are types where it makes sense to define the length, but it can never 60 | # be empty. This lint is reasonable for container-like data-structures, but 61 | # makes less sense for hardware-backed data structures. 62 | "-Aclippy::len_without_is_empty", 63 | # While it's generally a good idea to derive / implement `Default` when 64 | # appropriate, there are many types in the HvLite codebase where a type has a 65 | # `new()` method, but no sensible "Default". 66 | "-Aclippy::new_without_default", 67 | # This is the #1 most commonly ignored lint for a reason (at least according 68 | # to [this famous issue](https://github.com/rust-lang/rust-clippy/issues/5418) 69 | # on the clippy GitHub repo)! There are plenty of perfectly reasonable 70 | # functions that require a large number of non-optional arguments, 71 | # particularly when working with low-level hardware APIs. 72 | "-Aclippy::too_many_arguments", 73 | # Pointer casts are harder to grok than an equivalent \*explicitly annotated\* 74 | # `transmute`. Emphasis on the fact that the transmute should _not_ infer the 75 | # underlying types, and that the turbofish operator should be used to clearly 76 | # specify which types are being transmuted. 77 | # 78 | # e.g: `let callback = transmute::<*const c_void, *const fn() -> c_int>` 79 | "-Aclippy::transmutes_expressible_as_ptr_casts", 80 | # This is a heuristic based lint that isn't always appropriate. While it's 81 | # often a good to decompose complex types into more digestible chunks, there 82 | # are many cases where a one-off complex type is required, and suppressing 83 | # this lint will simply add line-noise. 84 | "-Aclippy::type_complexity", 85 | # This lint attempts to simplify usages of if let usage in a loop where only 86 | # one variant type is used. While somewhat useful, its suggestions can lead to 87 | # throwing away potentially useful error information in non-obvious ways. 88 | "-Aclippy::manual_flatten", 89 | # This lint warns about comparing boolean values in an `assert_eq!` statement when `assert!` 90 | # could be used instead. While shorter, the explicit comparison can be more obvious to read 91 | # in certain cases than unary operators with `assert!`. 92 | "-Aclippy::bool_assert_comparison", 93 | # This lint suggests collapsing Box::new(Foo::default()) into Box::default(). We often 94 | # prefer to specify types completely for local code clarity's sake. 95 | "-Aclippy::box_default", 96 | # This lint is purely style, and we are ok with inlined and uninlined format args. 97 | "-Aclippy::uninlined_format_args", 98 | 99 | ### ENABLE_IN_CI "-Dwarnings", 100 | 101 | ] -------------------------------------------------------------------------------- /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | # Profile for CI runs. 5 | [profile.ci] 6 | # Print out output for failing tests as soon as they fail, and also at the end 7 | # of the run (for easy scrollability). 8 | failure-output = "immediate-final" 9 | # Do not cancel the test run on the first failure. 10 | fail-fast = false -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | name: Rust 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | RUST_TOOLCHAIN: 1.86.0 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | features: ["", "--no-default-features"] 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: dtolnay/rust-toolchain@master 25 | with: 26 | toolchain: ${{ env.RUST_TOOLCHAIN }} 27 | components: rustfmt, clippy 28 | - uses: taiki-e/install-action@nextest 29 | - name: Format 30 | run: cargo fmt --all -- --check 31 | - name: Check 32 | run: cargo check --all-features --all-targets 33 | - name: Clippy 34 | run: cargo clippy --all-features --all-targets -- -D warnings 35 | - name: Clippy igvm_defs no features 36 | run: cargo clippy -p igvm_defs -- -D warnings 37 | - name: Build 38 | run: cargo build --verbose ${{ matrix.features }} 39 | - name: Run unit tests 40 | run: cargo nextest run --verbose ${{ matrix.features }} --profile ci --workspace 41 | - name: Run doc tests 42 | run: cargo test --no-fail-fast --doc --workspace 43 | 44 | c_api: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v3 48 | - uses: dtolnay/rust-toolchain@master 49 | with: 50 | toolchain: ${{ env.RUST_TOOLCHAIN }} 51 | - name: Install cbindgen 52 | run: cargo install cbindgen 53 | - name: Install build dependencies 54 | run: sudo apt-get install -y libcunit1-dev 55 | - name: Build and test 56 | run: make -f igvm_c/Makefile 57 | 58 | miri: 59 | name: "Miri" 60 | strategy: 61 | matrix: 62 | features: ["", "--no-default-features"] 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v3 66 | - uses: taiki-e/install-action@nextest 67 | - name: Install Miri 68 | run: | 69 | rustup toolchain install nightly --component miri 70 | rustup override set nightly 71 | cargo miri setup 72 | - name: Test with Miri 73 | run: cargo miri nextest run ${{ matrix.features }} --profile ci --workspace 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | target_c/ 6 | 7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | # MSVC Windows builds of rustc generate these, which store debugging information 15 | *.pdb 16 | 17 | # Header files generated for the C API by igvm_c/Makefile 18 | igvm_c/include/*.h 19 | 20 | # Ignore vscode directories 21 | .vscode/ -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @microsoft/igvm-maintainers 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | [workspace] 5 | resolver = "2" 6 | members = [ 7 | "igvm", 8 | "igvm_c/test_data", 9 | ] 10 | 11 | [workspace.dependencies] 12 | igvm_defs = { path = "igvm_defs", version = "0.3.4" } 13 | igvm = { path = "igvm", version = "0.3.4" } 14 | 15 | anyhow = "1.0" 16 | bitfield-struct = "0.10" 17 | crc32fast = { version = "1.3.2", default-features = false } 18 | hex = { version = "0.4", default-features = false } 19 | open-enum = "0.5.2" 20 | range_map_vec = "0.2.0" 21 | static_assertions = "1.1" 22 | thiserror = "2.0" 23 | tracing = "0.1" 24 | zerocopy = { version = "0.8.14", features = ["derive"] } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # igvm 2 | 3 | [![crates.io](https://img.shields.io/crates/d/igvm?label=crates.io%2Figvm)](https://crates.io/crates/igvm) 4 | [![docs.rs](https://img.shields.io/docsrs/igvm?label=docs.rs%2Figvm)](https://docs.rs/igvm/) 5 | [![crates.io](https://img.shields.io/crates/d/igvm_defs?label=crates.io%2Figvm_defs)](https://crates.io/crates/igvm_defs) 6 | [![docs.rs](https://img.shields.io/docsrs/igvm_defs?label=docs.rs%2Figvm_defs)](https://docs.rs/igvm_defs/) 7 | 8 | This project is the home of the Independent Guest Virtual Machine (IGVM) file 9 | format. The format specification can be found in the 10 | [`igvm_defs`](https://crates.io/crates/igvm_defs) crate, with a Rust 11 | implementation of the binary format in the 12 | [`igvm`](https://crates.io/crates/igvm) crate. 13 | 14 | The IGVM file format is designed to encapsulate all information required to 15 | launch a virtual machine on any given virtualization stack, with support for 16 | different isolation technologies such as AMD SEV-SNP and Intel TDX. 17 | 18 | At a conceptual level, this file format is a set of commands created by the 19 | tool that generated the file, used by the loader to construct the initial 20 | guest state. The file format also contains measurement information that the 21 | underlying platform will use to confirm that the file was loaded correctly 22 | and signed by the appropriate authorities. 23 | 24 | ## Contributing 25 | 26 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 27 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 28 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 29 | 30 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 31 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 32 | provided by the bot. You will only need to do this once across all repos using our CLA. 33 | 34 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 35 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 36 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 37 | 38 | ## Trademarks 39 | 40 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 41 | trademarks or logos is subject to and must follow 42 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 43 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 44 | Any use of third-party trademarks or logos are subject to those third-party's policies. 45 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please 6 | search the existing issues before filing new issues to avoid duplicates. For 7 | new issues, file your bug or feature request as a new Issue. 8 | 9 | ## Microsoft Support Policy 10 | 11 | Support for the igvm project is limited to the resources listed above. 12 | -------------------------------------------------------------------------------- /igvm/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | [package] 5 | name = "igvm" 6 | version = "0.3.4" 7 | edition = "2021" 8 | description = "The igvm crate is an implementation of a parser for the Independent Guest Virtual Machine (IGVM) file format." 9 | license = "MIT" 10 | authors = ["Microsoft"] 11 | repository = "https://github.com/microsoft/igvm" 12 | keywords = ["virtualization"] 13 | categories = ["virtualization", "parser-implementations"] 14 | 15 | [package.metadata.docs.rs] 16 | # Document all features 17 | all-features = true 18 | # Defines the configuration attribute `docsrs` which emits nicer docs via 19 | # nightly features. 20 | # 21 | # Run locally with RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features 22 | rustdoc-args = ["--cfg", "docsrs"] 23 | 24 | [lib] 25 | name = "igvm" 26 | crate-type = ["staticlib", "rlib"] 27 | 28 | [dependencies] 29 | igvm_defs = { workspace = true, features = ["unstable"] } 30 | 31 | bitfield-struct.workspace = true 32 | range_map_vec.workspace = true 33 | crc32fast.workspace = true 34 | hex = { workspace = true, features = ["alloc"] } 35 | open-enum.workspace = true 36 | thiserror.workspace = true 37 | tracing.workspace = true 38 | zerocopy = { workspace = true, features = ["alloc"] } 39 | static_assertions.workspace = true 40 | 41 | [features] 42 | default = [] 43 | igvm-c = [] # Add exports that allow the library to be used from C 44 | -------------------------------------------------------------------------------- /igvm/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /igvm/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /igvm/src/c_api.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright (c) 2023 SUSE LLC 4 | // 5 | // Author: Roy Hopkins 6 | 7 | //! Provides an alternative interface for using the IGVM crate that 8 | //! is suitable for calling from C. 9 | 10 | // UNSAFETY: This module requires the use of 'unsafe' as it implements 11 | // extern "C" functions for providing a C API. 12 | #![allow(unsafe_code)] 13 | 14 | use std::collections::BTreeMap; 15 | use std::ptr::null; 16 | use std::sync::atomic::AtomicI32; 17 | use std::sync::atomic::Ordering; 18 | use std::sync::Mutex; 19 | use std::sync::MutexGuard; 20 | use std::sync::OnceLock; 21 | 22 | use crate::Error; 23 | use crate::FileDataSerializer; 24 | use crate::IgvmFile; 25 | use open_enum::open_enum; 26 | 27 | /// An enumeration of the possible results that can be returned from C API 28 | /// functions. Some of the extern "C" functions return a positive value 29 | /// representing a handle or a count on success, or an IgvmResult value on 30 | /// error. Therefore all error values must be negative. 31 | #[open_enum] 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 33 | #[repr(i32)] 34 | pub enum IgvmResult { 35 | IGVMAPI_OK = 0, 36 | IGVMAPI_INVALID_PARAMETER = -1, 37 | IGVMAPI_NO_DATA = -2, 38 | IGVMAPI_INVALID_FILE = -3, 39 | IGVMAPI_INVALID_HANDLE = -4, 40 | IGVMAPI_NO_PLATFORM_HEADERS = -5, 41 | IGVMAPI_FILE_DATA_SECTION_TOO_LARGE = -6, 42 | IGVMAPI_VARIABLE_HEADER_SECTION_TOO_LARGE = -7, 43 | IGVMAPI_TOTAL_FILE_SIZE_TOO_LARGE = -8, 44 | IGVMAPI_INVALID_BINARY_PLATFORM_HEADER = -9, 45 | IGVMAPI_INVALID_BINARY_INITIALIZATION_HEADER = -10, 46 | IGVMAPI_INVALID_BINARY_DIRECTIVE_HEADER = -11, 47 | IGVMAPI_MULTIPLE_PLATFORM_HEADERS_WITH_SAME_ISOLATION = -12, 48 | IGVMAPI_INVALID_PARAMETER_AREA_INDEX = -13, 49 | IGVMAPI_INVALID_PLATFORM_TYPE = -14, 50 | IGVMAPI_NO_FREE_COMPATIBILITY_MASKS = -15, 51 | IGVMAPI_INVALID_FIXED_HEADER = -16, 52 | IGVMAPI_INVALID_BINARY_VARIABLE_HEADER_SECTION = -17, 53 | IGVMAPI_INVALID_CHECKSUM = -18, 54 | IGVMAPI_MULTIPLE_PAGE_TABLE_RELOCATION_HEADERS = -19, 55 | IGVMAPI_RELOCATION_REGIONS_OVERLAP = -20, 56 | IGVMAPI_PARAMETER_INSERT_INSIDE_PAGE_TABLE_REGION = -21, 57 | IGVMAPI_NO_MATCHING_VP_CONTEXT = -22, 58 | IGVMAPI_PLATFORM_ARCH_UNSUPPORTED = -23, 59 | IGVMAPI_INVALID_HEADER_ARCH = -24, 60 | IGVMAPI_UNSUPPORTED_PAGE_SIZE = -25, 61 | IGVMAPI_INVALID_FIXED_HEADER_ARCH = -26, 62 | IGVMAPI_MERGE_REVISION = -27, 63 | } 64 | 65 | type IgvmHandle = i32; 66 | 67 | struct IgvmFileInstance { 68 | file: IgvmFile, 69 | buffers: BTreeMap>, 70 | } 71 | 72 | static IGVM_HANDLES: OnceLock>> = OnceLock::new(); 73 | static IGVM_HANDLE_FACTORY: OnceLock = OnceLock::new(); 74 | 75 | #[open_enum] 76 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 77 | #[repr(u32)] 78 | pub enum IgvmHeaderSection { 79 | HEADER_SECTION_PLATFORM, 80 | HEADER_SECTION_INITIALIZATION, 81 | HEADER_SECTION_DIRECTIVE, 82 | } 83 | 84 | struct IgvmFileHandleLock<'a> { 85 | lock: MutexGuard<'a, BTreeMap>, 86 | handle: IgvmHandle, 87 | } 88 | 89 | impl IgvmFileHandleLock<'_> { 90 | pub fn new(handle: IgvmHandle) -> Result { 91 | let lock = IGVM_HANDLES 92 | .get() 93 | .ok_or(IgvmResult::IGVMAPI_INVALID_HANDLE)? 94 | .lock() 95 | .unwrap(); 96 | 97 | Ok(IgvmFileHandleLock { lock, handle }) 98 | } 99 | 100 | pub fn get(&self) -> Result<&IgvmFileInstance, IgvmResult> { 101 | self.lock 102 | .get(&self.handle) 103 | .ok_or(IgvmResult::IGVMAPI_INVALID_HANDLE) 104 | } 105 | 106 | pub fn get_mut(&mut self) -> Result<&mut IgvmFileInstance, IgvmResult> { 107 | self.lock 108 | .get_mut(&self.handle) 109 | .ok_or(IgvmResult::IGVMAPI_INVALID_HANDLE) 110 | } 111 | } 112 | 113 | fn new_handle() -> i32 { 114 | IGVM_HANDLE_FACTORY 115 | .get_or_init(|| AtomicI32::new(1)) 116 | .fetch_add(1, Ordering::Relaxed) 117 | } 118 | 119 | fn translate_error(error: Error) -> IgvmResult { 120 | match error { 121 | Error::NoPlatformHeaders => IgvmResult::IGVMAPI_NO_PLATFORM_HEADERS, 122 | Error::FileDataSectionTooLarge => IgvmResult::IGVMAPI_FILE_DATA_SECTION_TOO_LARGE, 123 | Error::VariableHeaderSectionTooLarge => { 124 | IgvmResult::IGVMAPI_VARIABLE_HEADER_SECTION_TOO_LARGE 125 | } 126 | Error::TotalFileSizeTooLarge => IgvmResult::IGVMAPI_TOTAL_FILE_SIZE_TOO_LARGE, 127 | Error::InvalidBinaryPlatformHeader(_) => IgvmResult::IGVMAPI_INVALID_BINARY_PLATFORM_HEADER, 128 | Error::InvalidBinaryInitializationHeader(_) => { 129 | IgvmResult::IGVMAPI_INVALID_BINARY_INITIALIZATION_HEADER 130 | } 131 | Error::InvalidBinaryDirectiveHeader(_) => { 132 | IgvmResult::IGVMAPI_INVALID_BINARY_DIRECTIVE_HEADER 133 | } 134 | Error::MultiplePlatformHeadersWithSameIsolation => { 135 | IgvmResult::IGVMAPI_MULTIPLE_PLATFORM_HEADERS_WITH_SAME_ISOLATION 136 | } 137 | Error::InvalidParameterAreaIndex => IgvmResult::IGVMAPI_INVALID_PARAMETER_AREA_INDEX, 138 | Error::InvalidPlatformType => IgvmResult::IGVMAPI_INVALID_PLATFORM_TYPE, 139 | Error::NoFreeCompatibilityMasks => IgvmResult::IGVMAPI_NO_FREE_COMPATIBILITY_MASKS, 140 | Error::InvalidFixedHeader => IgvmResult::IGVMAPI_INVALID_FIXED_HEADER, 141 | Error::InvalidBinaryVariableHeaderSection => { 142 | IgvmResult::IGVMAPI_INVALID_BINARY_VARIABLE_HEADER_SECTION 143 | } 144 | Error::InvalidChecksum { 145 | expected: _, 146 | header_value: _, 147 | } => IgvmResult::IGVMAPI_INVALID_CHECKSUM, 148 | Error::MultiplePageTableRelocationHeaders => { 149 | IgvmResult::IGVMAPI_MULTIPLE_PAGE_TABLE_RELOCATION_HEADERS 150 | } 151 | Error::RelocationRegionsOverlap => IgvmResult::IGVMAPI_RELOCATION_REGIONS_OVERLAP, 152 | Error::ParameterInsertInsidePageTableRegion => { 153 | IgvmResult::IGVMAPI_PARAMETER_INSERT_INSIDE_PAGE_TABLE_REGION 154 | } 155 | Error::NoMatchingVpContext => IgvmResult::IGVMAPI_NO_MATCHING_VP_CONTEXT, 156 | Error::PlatformArchUnsupported { 157 | arch: _, 158 | platform: _, 159 | } => IgvmResult::IGVMAPI_PLATFORM_ARCH_UNSUPPORTED, 160 | Error::InvalidHeaderArch { 161 | arch: _, 162 | header_type: _, 163 | } => IgvmResult::IGVMAPI_INVALID_HEADER_ARCH, 164 | Error::UnsupportedPageSize(_) => IgvmResult::IGVMAPI_UNSUPPORTED_PAGE_SIZE, 165 | Error::InvalidFixedHeaderArch(_) => IgvmResult::IGVMAPI_INVALID_FIXED_HEADER_ARCH, 166 | Error::MergeRevision => IgvmResult::IGVMAPI_MERGE_REVISION, 167 | } 168 | } 169 | 170 | fn igvm_create(file: IgvmFile) -> IgvmHandle { 171 | let handle = new_handle(); 172 | let mut m = IGVM_HANDLES 173 | .get_or_init(|| Mutex::new(BTreeMap::new())) 174 | .lock() 175 | .unwrap(); 176 | m.insert( 177 | handle, 178 | IgvmFileInstance { 179 | file, 180 | buffers: BTreeMap::new(), 181 | }, 182 | ); 183 | handle 184 | } 185 | 186 | /// Returns a pointer to the array of bytes in a buffer. 187 | fn get_buffer(igvm_handle: IgvmHandle, buffer_handle: IgvmHandle) -> Result<*const u8, IgvmResult> { 188 | let handle_lock = IgvmFileHandleLock::new(igvm_handle)?; 189 | let igvm = handle_lock.get()?; 190 | Ok(igvm 191 | .buffers 192 | .get(&buffer_handle) 193 | .ok_or(IgvmResult::IGVMAPI_INVALID_HANDLE)? 194 | .as_ptr()) 195 | } 196 | 197 | /// Returns the size of a buffer. 198 | fn get_buffer_size(igvm_handle: IgvmHandle, buffer_handle: IgvmHandle) -> Result { 199 | let handle_lock = IgvmFileHandleLock::new(igvm_handle)?; 200 | let igvm = handle_lock.get()?; 201 | Ok(igvm 202 | .buffers 203 | .get(&buffer_handle) 204 | .ok_or(IgvmResult::IGVMAPI_INVALID_HANDLE)? 205 | .len() as i32) 206 | } 207 | 208 | /// Frees a buffer. 209 | fn free_buffer(igvm_handle: IgvmHandle, buffer_handle: IgvmHandle) -> Result<(), IgvmResult> { 210 | let mut handle_lock = IgvmFileHandleLock::new(igvm_handle)?; 211 | let igvm = handle_lock.get_mut()?; 212 | igvm.buffers.remove(&buffer_handle); 213 | Ok(()) 214 | } 215 | 216 | /// Get the count of headers for a particular section in a previously parsed 217 | /// IGVM file. 218 | fn header_count(handle: IgvmHandle, section: IgvmHeaderSection) -> Result { 219 | let mut handle_lock = IgvmFileHandleLock::new(handle)?; 220 | let igvm = handle_lock.get_mut()?; 221 | match section { 222 | IgvmHeaderSection::HEADER_SECTION_PLATFORM => Ok(igvm.file.platform_headers.len() as i32), 223 | IgvmHeaderSection::HEADER_SECTION_INITIALIZATION => { 224 | Ok(igvm.file.initialization_headers.len() as i32) 225 | } 226 | IgvmHeaderSection::HEADER_SECTION_DIRECTIVE => Ok(igvm.file.directive_headers.len() as i32), 227 | _ => Err(IgvmResult::IGVMAPI_INVALID_PARAMETER), 228 | } 229 | } 230 | 231 | /// Get the header type for the entry with the given index for a particular 232 | /// section in a previously parsed IGVM file. 233 | fn get_header_type( 234 | handle: IgvmHandle, 235 | section: IgvmHeaderSection, 236 | index: u32, 237 | ) -> Result { 238 | let mut handle_lock = IgvmFileHandleLock::new(handle)?; 239 | let igvm = handle_lock.get_mut()?; 240 | match section { 241 | IgvmHeaderSection::HEADER_SECTION_PLATFORM => Ok(igvm 242 | .file 243 | .platform_headers 244 | .get(index as usize) 245 | .ok_or(IgvmResult::IGVMAPI_INVALID_PARAMETER)? 246 | .header_type() 247 | .0 as i32), 248 | IgvmHeaderSection::HEADER_SECTION_INITIALIZATION => Ok(igvm 249 | .file 250 | .initialization_headers 251 | .get(index as usize) 252 | .ok_or(IgvmResult::IGVMAPI_INVALID_PARAMETER)? 253 | .header_type() 254 | .0 as i32), 255 | IgvmHeaderSection::HEADER_SECTION_DIRECTIVE => Ok(igvm 256 | .file 257 | .directive_headers 258 | .get(index as usize) 259 | .ok_or(IgvmResult::IGVMAPI_INVALID_PARAMETER)? 260 | .header_type() 261 | .0 as i32), 262 | _ => Err(IgvmResult::IGVMAPI_INVALID_PARAMETER), 263 | } 264 | } 265 | 266 | /// Prepare a buffer containing the header data in binary form for the entry 267 | /// with the given index for a particular section in a previously parsed IGVM 268 | /// file. 269 | fn get_header( 270 | handle: IgvmHandle, 271 | section: IgvmHeaderSection, 272 | index: u32, 273 | ) -> Result { 274 | let mut header_binary = Vec::::new(); 275 | 276 | let mut handle_lock = IgvmFileHandleLock::new(handle)?; 277 | let igvm = handle_lock.get_mut()?; 278 | 279 | match section { 280 | IgvmHeaderSection::HEADER_SECTION_PLATFORM => { 281 | igvm.file 282 | .platform_headers 283 | .get(index as usize) 284 | .ok_or(IgvmResult::IGVMAPI_INVALID_PARAMETER)? 285 | .write_binary_header(&mut header_binary) 286 | .map_err(|_| IgvmResult::IGVMAPI_INVALID_FILE)?; 287 | } 288 | IgvmHeaderSection::HEADER_SECTION_INITIALIZATION => { 289 | igvm.file 290 | .initialization_headers 291 | .get(index as usize) 292 | .ok_or(IgvmResult::IGVMAPI_INVALID_PARAMETER)? 293 | .write_binary_header(&mut header_binary) 294 | .map_err(|_| IgvmResult::IGVMAPI_INVALID_FILE)?; 295 | } 296 | IgvmHeaderSection::HEADER_SECTION_DIRECTIVE => { 297 | igvm.file 298 | .directive_headers 299 | .get(index as usize) 300 | .ok_or(IgvmResult::IGVMAPI_INVALID_PARAMETER)? 301 | .write_binary_header(&mut header_binary, &mut FileDataSerializer::new(0)) 302 | .map_err(|_| IgvmResult::IGVMAPI_INVALID_FILE)?; 303 | } 304 | _ => { 305 | return Err(IgvmResult::IGVMAPI_INVALID_PARAMETER); 306 | } 307 | } 308 | let header_handle = new_handle(); 309 | igvm.buffers.insert(header_handle, header_binary); 310 | Ok(header_handle) 311 | } 312 | 313 | /// Prepare a buffer containing the associated file data in binary form for the 314 | /// entry with the given index for a particular section in a previously parsed 315 | /// IGVM file. 316 | fn get_header_data( 317 | handle: IgvmHandle, 318 | section: IgvmHeaderSection, 319 | index: u32, 320 | ) -> Result { 321 | let mut handle_lock = IgvmFileHandleLock::new(handle)?; 322 | let igvm = handle_lock.get_mut()?; 323 | let mut header_data = FileDataSerializer::new(0); 324 | 325 | if section == IgvmHeaderSection::HEADER_SECTION_DIRECTIVE { 326 | let header = igvm 327 | .file 328 | .directive_headers 329 | .get(index as usize) 330 | .ok_or(IgvmResult::IGVMAPI_INVALID_PARAMETER)?; 331 | header 332 | .write_binary_header(&mut Vec::::new(), &mut header_data) 333 | .map_err(|_| IgvmResult::IGVMAPI_INVALID_FILE)?; 334 | } else { 335 | return Err(IgvmResult::IGVMAPI_INVALID_PARAMETER); 336 | } 337 | 338 | let header_data = header_data.take(); 339 | 340 | if header_data.is_empty() { 341 | Err(IgvmResult::IGVMAPI_NO_DATA) 342 | } else { 343 | let header_data_handle = new_handle(); 344 | igvm.buffers.insert(header_data_handle, header_data); 345 | Ok(header_data_handle) 346 | } 347 | } 348 | 349 | /// Returns a pointer to the array of bytes in a buffer. 350 | /// 351 | /// # Safety 352 | /// 353 | /// The caller must ensure that the buffer handle remains valid for the duration 354 | /// of accessing the data pointed to by the raw pointer returned by this 355 | /// function. This requires that the `buffer_handle` is not freed with a call to 356 | /// [`igvm_free_buffer()`] and that the `igvm_handle` is not freed with a call 357 | /// to [`igvm_free()`]. 358 | /// 359 | /// Invalid handles are handled within this function and result in a return 360 | /// value of `null()`. The caller must check the result before using the array. 361 | #[no_mangle] 362 | pub unsafe extern "C" fn igvm_get_buffer( 363 | igvm_handle: IgvmHandle, 364 | buffer_handle: IgvmHandle, 365 | ) -> *const u8 { 366 | match get_buffer(igvm_handle, buffer_handle) { 367 | Ok(p) => p, 368 | Err(_) => null(), 369 | } 370 | } 371 | 372 | /// Returns the size of a buffer. 373 | /// 374 | /// If either handle is invalid or if there is an error then the return value will 375 | /// be IGVMAPI_INVALID_HANDLE 376 | #[no_mangle] 377 | pub extern "C" fn igvm_get_buffer_size(igvm_handle: IgvmHandle, buffer_handle: IgvmHandle) -> i32 { 378 | match get_buffer_size(igvm_handle, buffer_handle) { 379 | Ok(len) => len, 380 | Err(e) => e.0, 381 | } 382 | } 383 | 384 | /// Frees a buffer. 385 | /// 386 | /// If either handle is invalid then the function has no effect. 387 | #[no_mangle] 388 | pub extern "C" fn igvm_free_buffer(igvm_handle: IgvmHandle, buffer_handle: IgvmHandle) { 389 | let _ = free_buffer(igvm_handle, buffer_handle); 390 | } 391 | 392 | /// Parse a binary array containing an IGVM file. The contents of the file are 393 | /// validated and, if valid, a handle is returned to represent the file. This 394 | /// handle must be freed with a call to igvm_free(). 395 | /// 396 | /// If any error occurs then the returned handle will be less than zero and will 397 | /// match one of the IGVMAPI error codes. 398 | /// 399 | /// # Safety 400 | /// 401 | /// The function assumes that there are at least `len` valid bytes of memory 402 | /// starting at the address pointed to by `data`. If this is violated then this 403 | /// will result in undefined behaviour. 404 | #[no_mangle] 405 | pub unsafe extern "C" fn igvm_new_from_binary(data: *const u8, len: u32) -> IgvmHandle { 406 | // SAFETY: Caller guarantees that the data ptr is an array with at least len bytes of memory. 407 | let file_data = unsafe { std::slice::from_raw_parts(data, len as usize) }; 408 | let result = IgvmFile::new_from_binary(file_data, None); 409 | 410 | match result { 411 | Ok(file) => igvm_create(file), 412 | Err(e) => translate_error(e).0, 413 | } 414 | } 415 | 416 | /// Free a handle that was created with a prevoius call to 417 | /// [`igvm_new_from_binary()`]. 418 | #[no_mangle] 419 | pub extern "C" fn igvm_free(handle: IgvmHandle) { 420 | if let Some(handles) = IGVM_HANDLES.get() { 421 | let _ = handles.lock().unwrap().remove(&handle); 422 | } 423 | } 424 | 425 | /// Get the count of headers for a particular section in a previously parsed 426 | /// IGVM file. 427 | /// 428 | /// If any error occurs then the returned value will be less than zero and will 429 | /// match one of the IGVMAPI error codes. 430 | #[no_mangle] 431 | pub extern "C" fn igvm_header_count(handle: IgvmHandle, section: IgvmHeaderSection) -> i32 { 432 | header_count(handle, section) 433 | .or_else(|e| Ok(e.0) as Result) 434 | .unwrap() 435 | } 436 | 437 | /// Get the header type for the entry with the given index for a particular 438 | /// section in a previously parsed IGVM file. 439 | /// 440 | /// If any error occurs then the returned value will be less than zero and will 441 | /// match one of the IGVMAPI error codes. 442 | #[no_mangle] 443 | pub extern "C" fn igvm_get_header_type( 444 | handle: IgvmHandle, 445 | section: IgvmHeaderSection, 446 | index: u32, 447 | ) -> i32 { 448 | get_header_type(handle, section, index) 449 | .or_else(|e| Ok(e.0) as Result) 450 | .unwrap() 451 | } 452 | 453 | /// Prepare a buffer containing the header data in binary form for the entry 454 | /// with the given index for a particular section in a previously parsed IGVM 455 | /// file. 456 | /// 457 | /// The buffer containing the data is returned via a handle from this function. 458 | /// The handle can be used to access a raw pointer to the data and to query its 459 | /// size. The buffer handle remains valid until it is closed with a call to 460 | /// [`igvm_free_buffer()`] or the parsed file handle is closed with a call to 461 | /// [`igvm_free()`]. 462 | /// 463 | /// If any error occurs then the returned value will be less than zero and will 464 | /// match one of the IGVMAPI error codes. 465 | #[no_mangle] 466 | pub extern "C" fn igvm_get_header( 467 | handle: IgvmHandle, 468 | section: IgvmHeaderSection, 469 | index: u32, 470 | ) -> IgvmHandle { 471 | get_header(handle, section, index) 472 | .or_else(|e| Ok(e.0) as Result) 473 | .unwrap() 474 | } 475 | 476 | /// Prepare a buffer containing the associated file data in binary form for the 477 | /// entry with the given index for a particular section in a previously parsed 478 | /// IGVM file. 479 | /// 480 | /// The buffer containing the data is returned via a handle from this function. 481 | /// The handle can be used to access a raw pointer to the data and to query its 482 | /// size. The buffer handle remains valid until it is closed with a call to 483 | /// [`igvm_free_buffer()`] or the parsed file handle is closed with a call to 484 | /// [`igvm_free()`]. 485 | /// 486 | /// If any error occurs then the returned value will be less than zero and will 487 | /// match one of the IGVMAPI error codes. 488 | #[no_mangle] 489 | pub extern "C" fn igvm_get_header_data( 490 | handle: IgvmHandle, 491 | section: IgvmHeaderSection, 492 | index: u32, 493 | ) -> IgvmHandle { 494 | get_header_data(handle, section, index) 495 | .or_else(|e| Ok(e.0) as Result) 496 | .unwrap() 497 | } 498 | 499 | #[cfg(test)] 500 | mod tests { 501 | use super::igvm_get_header; 502 | use super::igvm_get_header_data; 503 | use super::igvm_new_from_binary; 504 | use crate::c_api::igvm_free; 505 | use crate::c_api::igvm_free_buffer; 506 | use crate::c_api::igvm_get_buffer; 507 | use crate::c_api::igvm_get_buffer_size; 508 | use crate::c_api::igvm_header_count; 509 | use crate::c_api::IgvmHeaderSection; 510 | use crate::c_api::IgvmResult; 511 | use crate::IgvmDirectiveHeader; 512 | use crate::IgvmFile; 513 | use crate::IgvmInitializationHeader; 514 | use crate::IgvmPlatformHeader; 515 | use crate::IgvmRevision; 516 | use igvm_defs::IgvmPageDataFlags; 517 | use igvm_defs::IgvmPageDataType; 518 | use igvm_defs::IgvmPlatformType; 519 | use igvm_defs::IgvmVariableHeaderType; 520 | use igvm_defs::IGVM_VHS_GUEST_POLICY; 521 | use igvm_defs::IGVM_VHS_PAGE_DATA; 522 | use igvm_defs::IGVM_VHS_PARAMETER; 523 | use igvm_defs::IGVM_VHS_PARAMETER_AREA; 524 | use igvm_defs::IGVM_VHS_PARAMETER_INSERT; 525 | use igvm_defs::IGVM_VHS_SUPPORTED_PLATFORM; 526 | use igvm_defs::IGVM_VHS_VARIABLE_HEADER; 527 | use igvm_defs::PAGE_SIZE_4K; 528 | use std::mem::size_of; 529 | 530 | fn new_platform( 531 | compatibility_mask: u32, 532 | platform_type: IgvmPlatformType, 533 | ) -> IgvmPlatformHeader { 534 | IgvmPlatformHeader::SupportedPlatform(IGVM_VHS_SUPPORTED_PLATFORM { 535 | compatibility_mask, 536 | highest_vtl: 0, 537 | platform_type, 538 | platform_version: 1, 539 | shared_gpa_boundary: 0, 540 | }) 541 | } 542 | 543 | fn new_guest_policy(policy: u64, compatibility_mask: u32) -> IgvmInitializationHeader { 544 | IgvmInitializationHeader::GuestPolicy { 545 | policy, 546 | compatibility_mask, 547 | } 548 | } 549 | 550 | fn new_page_data(page: u64, compatibility_mask: u32, data: &[u8]) -> IgvmDirectiveHeader { 551 | IgvmDirectiveHeader::PageData { 552 | gpa: page * PAGE_SIZE_4K, 553 | compatibility_mask, 554 | flags: IgvmPageDataFlags::new(), 555 | data_type: IgvmPageDataType::NORMAL, 556 | data: data.to_vec(), 557 | } 558 | } 559 | 560 | fn new_parameter_area(index: u32) -> IgvmDirectiveHeader { 561 | IgvmDirectiveHeader::ParameterArea { 562 | number_of_bytes: 4096, 563 | parameter_area_index: index, 564 | initial_data: vec![], 565 | } 566 | } 567 | 568 | fn new_parameter_usage(index: u32) -> IgvmDirectiveHeader { 569 | IgvmDirectiveHeader::VpCount(IGVM_VHS_PARAMETER { 570 | parameter_area_index: index, 571 | byte_offset: 0, 572 | }) 573 | } 574 | 575 | fn new_parameter_insert(page: u64, index: u32, mask: u32) -> IgvmDirectiveHeader { 576 | IgvmDirectiveHeader::ParameterInsert(IGVM_VHS_PARAMETER_INSERT { 577 | gpa: page * PAGE_SIZE_4K, 578 | parameter_area_index: index, 579 | compatibility_mask: mask, 580 | }) 581 | } 582 | 583 | fn create_igvm() -> Vec { 584 | let data1 = vec![1; PAGE_SIZE_4K as usize]; 585 | let data2 = vec![2; PAGE_SIZE_4K as usize]; 586 | let data3 = vec![3; PAGE_SIZE_4K as usize]; 587 | let data4 = vec![4; PAGE_SIZE_4K as usize]; 588 | let file = IgvmFile::new( 589 | IgvmRevision::V1, 590 | vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)], 591 | vec![new_guest_policy(0x30000, 1), new_guest_policy(0x30000, 2)], 592 | vec![ 593 | new_page_data(0, 1, &data1), 594 | new_page_data(1, 1, &data2), 595 | new_page_data(2, 1, &data3), 596 | new_page_data(4, 1, &data4), 597 | new_page_data(10, 1, &data1), 598 | new_page_data(11, 1, &data2), 599 | new_page_data(12, 1, &data3), 600 | new_page_data(14, 1, &data4), 601 | new_parameter_area(0), 602 | new_parameter_usage(0), 603 | new_parameter_insert(20, 0, 1), 604 | ], 605 | ) 606 | .expect("Failed to create file"); 607 | let mut binary_file = Vec::new(); 608 | file.serialize(&mut binary_file).unwrap(); 609 | binary_file 610 | } 611 | 612 | #[test] 613 | fn c_api_parse_valid_file() { 614 | let file_data = create_igvm(); 615 | // SAFETY: The function call requires the size of the buffer to be at least as 616 | // large as the specified length. We are passing a pointer to a vector along with 617 | // the length of the vector so the safety requirements are met. 618 | let igvm = unsafe { igvm_new_from_binary(file_data.as_ptr(), file_data.len() as u32) }; 619 | assert!(igvm > 0); 620 | igvm_free(igvm); 621 | } 622 | 623 | #[test] 624 | fn c_api_test_invalid_fixed_header() { 625 | let invalid_igvm_buf: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; 626 | // SAFETY: The function call requires the size of the buffer to be at least as 627 | // large as the specified length. We are passing a pointer to a fixed array along with 628 | // the length of the same fixed array so the safety requirements are met. 629 | let igvm = unsafe { 630 | igvm_new_from_binary(invalid_igvm_buf.as_ptr(), invalid_igvm_buf.len() as u32) 631 | }; 632 | assert_eq!(igvm, IgvmResult::IGVMAPI_INVALID_FIXED_HEADER.0); 633 | } 634 | 635 | #[test] 636 | fn c_api_test_header_counts() { 637 | let file_data = create_igvm(); 638 | // SAFETY: The function call requires the size of the buffer to be at least as 639 | // large as the specified length. We are passing a pointer to a vector along with 640 | // the length of the vector so the safety requirements are met. 641 | let igvm = unsafe { igvm_new_from_binary(file_data.as_ptr(), file_data.len() as u32) }; 642 | assert_eq!( 643 | igvm_header_count(igvm, IgvmHeaderSection::HEADER_SECTION_PLATFORM), 644 | 1 645 | ); 646 | assert_eq!( 647 | igvm_header_count(igvm, IgvmHeaderSection::HEADER_SECTION_INITIALIZATION), 648 | 2 649 | ); 650 | assert_eq!( 651 | igvm_header_count(igvm, IgvmHeaderSection::HEADER_SECTION_DIRECTIVE), 652 | 11 653 | ); 654 | igvm_free(igvm); 655 | } 656 | 657 | #[test] 658 | fn c_api_test_platform_header() { 659 | let file_data = create_igvm(); 660 | // SAFETY: The function call requires the size of the buffer to be at least as 661 | // large as the specified length. We are passing a pointer to a vector along with 662 | // the length of the vector so the safety requirements are met. 663 | let igvm = unsafe { igvm_new_from_binary(file_data.as_ptr(), file_data.len() as u32) }; 664 | 665 | let data = igvm_get_header(igvm, IgvmHeaderSection::HEADER_SECTION_PLATFORM, 0); 666 | assert!(data > 0); 667 | 668 | // SAFETY: igvm_get_buffer returns a raw pointer that can only be used up until 669 | // igvm_free_buffer() is called. This is satisfied by the code below. In addition, the 670 | // returned pointer is safely cast to the required type only after checking the 671 | // type reported in the header. 672 | unsafe { 673 | let header = igvm_get_buffer(igvm, data) as *const IGVM_VHS_VARIABLE_HEADER; 674 | assert_eq!( 675 | (*header).typ, 676 | IgvmVariableHeaderType::IGVM_VHT_SUPPORTED_PLATFORM 677 | ); 678 | assert_eq!( 679 | (*header).length, 680 | size_of::() as u32 681 | ); 682 | 683 | let platform = header.add(1) as *const IGVM_VHS_SUPPORTED_PLATFORM; 684 | assert_eq!((*platform).platform_type, IgvmPlatformType::VSM_ISOLATION); 685 | assert_eq!((*platform).compatibility_mask, 1); 686 | assert_eq!((*platform).platform_version, 1); 687 | assert_eq!((*platform).highest_vtl, 0); 688 | assert_eq!((*platform).shared_gpa_boundary, 0); 689 | } 690 | igvm_free_buffer(igvm, data); 691 | 692 | igvm_free(igvm); 693 | } 694 | 695 | #[test] 696 | fn c_api_test_initialization_header() { 697 | let file_data = create_igvm(); 698 | // SAFETY: The function call requires the size of the buffer to be at least as 699 | // large as the specified length. We are passing a pointer to a vector along with 700 | // the length of the vector so the safety requirements are met. 701 | let igvm = unsafe { igvm_new_from_binary(file_data.as_ptr(), file_data.len() as u32) }; 702 | 703 | let data = igvm_get_header(igvm, IgvmHeaderSection::HEADER_SECTION_INITIALIZATION, 0); 704 | assert!(data > 0); 705 | // SAFETY: igvm_get_buffer returns a raw pointer that can only be used up until 706 | // igvm_free_buffer() is called. This is satisfied by the code below. In addition, the 707 | // returned pointer is safely cast to the required type only after checking the 708 | // type reported in the header. 709 | unsafe { 710 | let header = igvm_get_buffer(igvm, data) as *const IGVM_VHS_VARIABLE_HEADER; 711 | assert_eq!((*header).typ, IgvmVariableHeaderType::IGVM_VHT_GUEST_POLICY); 712 | assert_eq!((*header).length, size_of::() as u32); 713 | 714 | let policy = header.add(1) as *const IGVM_VHS_GUEST_POLICY; 715 | assert_eq!((*policy).policy, 0x30000); 716 | assert_eq!((*policy).compatibility_mask, 1); 717 | assert_eq!((*policy).reserved, 0); 718 | } 719 | igvm_free_buffer(igvm, data); 720 | 721 | let data = igvm_get_header(igvm, IgvmHeaderSection::HEADER_SECTION_INITIALIZATION, 1); 722 | assert!(data > 0); 723 | // SAFETY: igvm_get_buffer returns a raw pointer that can only be used up until 724 | // igvm_free_buffer() is called. This is satisfied by the code below. In addition, the 725 | // returned pointer is safely cast to the required type only after checking the 726 | // type reported in the header. 727 | unsafe { 728 | let header = igvm_get_buffer(igvm, data) as *const IGVM_VHS_VARIABLE_HEADER; 729 | assert_eq!((*header).typ, IgvmVariableHeaderType::IGVM_VHT_GUEST_POLICY); 730 | assert_eq!((*header).length, size_of::() as u32); 731 | 732 | let policy = header.add(1) as *const IGVM_VHS_GUEST_POLICY; 733 | assert_eq!((*policy).policy, 0x30000); 734 | assert_eq!((*policy).compatibility_mask, 2); 735 | assert_eq!((*policy).reserved, 0); 736 | } 737 | igvm_free_buffer(igvm, data); 738 | 739 | igvm_free(igvm); 740 | } 741 | 742 | #[test] 743 | fn c_api_test_directive_header() { 744 | let file_data = create_igvm(); 745 | // SAFETY: The function call requires the size of the buffer to be at least as 746 | // large as the specified length. We are passing a pointer to a vector along with 747 | // the length of the vector so the safety requirements are met. 748 | let igvm = unsafe { igvm_new_from_binary(file_data.as_ptr(), file_data.len() as u32) }; 749 | 750 | let data = igvm_get_header(igvm, IgvmHeaderSection::HEADER_SECTION_DIRECTIVE, 1); 751 | assert!(data > 0); 752 | // SAFETY: igvm_get_buffer returns a raw pointer that can only be used up until 753 | // igvm_free_buffer() is called. This is satisfied by the code below. In addition, the 754 | // returned pointer is safely cast to the required type only after checking the 755 | // type reported in the header. 756 | unsafe { 757 | let header = igvm_get_buffer(igvm, data) as *const IGVM_VHS_VARIABLE_HEADER; 758 | assert_eq!((*header).typ, IgvmVariableHeaderType::IGVM_VHT_PAGE_DATA); 759 | assert_eq!((*header).length, size_of::() as u32); 760 | 761 | let page = header.add(1) as *const IGVM_VHS_PAGE_DATA; 762 | assert_eq!((*page).data_type, IgvmPageDataType::NORMAL); 763 | assert_eq!((*page).compatibility_mask, 1); 764 | assert_eq!((*page).file_offset, 0); 765 | assert_eq!((*page).flags.is_2mb_page(), false); 766 | assert_eq!((*page).flags.unmeasured(), false); 767 | assert_eq!((*page).flags.reserved(), 0); 768 | assert_eq!((*page).gpa, 0x1000); 769 | assert_eq!((*page).reserved, 0); 770 | } 771 | igvm_free_buffer(igvm, data); 772 | 773 | let data = igvm_get_header(igvm, IgvmHeaderSection::HEADER_SECTION_DIRECTIVE, 8); 774 | assert!(data > 0); 775 | // SAFETY: igvm_get_buffer returns a raw pointer that can only be used up until 776 | // igvm_free_buffer() is called. This is satisfied by the code below. In addition, the 777 | // returned pointer is safely cast to the required type only after checking the 778 | // type reported in the header. 779 | unsafe { 780 | let header = igvm_get_buffer(igvm, data) as *const IGVM_VHS_VARIABLE_HEADER; 781 | assert_eq!( 782 | (*header).typ, 783 | IgvmVariableHeaderType::IGVM_VHT_PARAMETER_AREA 784 | ); 785 | assert_eq!( 786 | (*header).length, 787 | size_of::() as u32 788 | ); 789 | 790 | let param_area = header.add(1) as *const IGVM_VHS_PARAMETER_AREA; 791 | assert_eq!((*param_area).parameter_area_index, 0); 792 | assert_eq!((*param_area).file_offset, 0); 793 | assert_eq!((*param_area).number_of_bytes, 0x1000); 794 | } 795 | igvm_free_buffer(igvm, data); 796 | 797 | let data = igvm_get_header(igvm, IgvmHeaderSection::HEADER_SECTION_DIRECTIVE, 9); 798 | assert!(data > 0); 799 | // SAFETY: igvm_get_buffer returns a raw pointer that can only be used up until 800 | // igvm_free_buffer() is called. This is satisfied by the code below. In addition, the 801 | // returned pointer is safely cast to the required type only after checking the 802 | // type reported in the header. 803 | unsafe { 804 | let header = igvm_get_buffer(igvm, data) as *const IGVM_VHS_VARIABLE_HEADER; 805 | assert_eq!( 806 | (*header).typ, 807 | IgvmVariableHeaderType::IGVM_VHT_VP_COUNT_PARAMETER 808 | ); 809 | assert_eq!((*header).length, size_of::() as u32); 810 | 811 | let param = header.add(1) as *const IGVM_VHS_PARAMETER; 812 | assert_eq!((*param).parameter_area_index, 0); 813 | assert_eq!((*param).byte_offset, 0); 814 | } 815 | igvm_free_buffer(igvm, data); 816 | 817 | let data = igvm_get_header(igvm, IgvmHeaderSection::HEADER_SECTION_DIRECTIVE, 10); 818 | assert!(data > 0); 819 | // SAFETY: igvm_get_buffer returns a raw pointer that can only be used up until 820 | // igvm_free_buffer() is called. This is satisfied by the code below. In addition, the 821 | // returned pointer is safely cast to the required type only after checking the 822 | // type reported in the header. 823 | unsafe { 824 | let header = igvm_get_buffer(igvm, data) as *const IGVM_VHS_VARIABLE_HEADER; 825 | assert_eq!( 826 | (*header).typ, 827 | IgvmVariableHeaderType::IGVM_VHT_PARAMETER_INSERT 828 | ); 829 | assert_eq!( 830 | (*header).length, 831 | size_of::() as u32 832 | ); 833 | 834 | let ins = header.add(1) as *const IGVM_VHS_PARAMETER_INSERT; 835 | assert_eq!((*ins).parameter_area_index, 0); 836 | assert_eq!((*ins).compatibility_mask, 1); 837 | assert_eq!((*ins).gpa, 0x14000); 838 | } 839 | igvm_free_buffer(igvm, data); 840 | 841 | igvm_free(igvm); 842 | } 843 | 844 | #[test] 845 | fn c_api_test_associated_data() { 846 | let file_data = create_igvm(); 847 | // SAFETY: The function call requires the size of the buffer to be at least as 848 | // large as the specified length. We are passing a pointer to a vector along with 849 | // the length of the vector so the safety requirements are met. 850 | let igvm = unsafe { igvm_new_from_binary(file_data.as_ptr(), file_data.len() as u32) }; 851 | 852 | let data = igvm_get_header_data(igvm, IgvmHeaderSection::HEADER_SECTION_DIRECTIVE, 3); 853 | assert!(data > 0); 854 | let data_length = igvm_get_buffer_size(igvm, data); 855 | assert_eq!(data_length, 0x1000); 856 | // SAFETY: igvm_get_buffer returns a raw pointer that can only be used up until 857 | // igvm_free_buffer() is called. This is satisfied by the code below. In addition, the 858 | // returned pointer is safely cast to the required type only after checking the 859 | // type reported in the header. 860 | unsafe { 861 | let buf = igvm_get_buffer(igvm, data); 862 | for i in 0..data_length as usize { 863 | assert_eq!(buf.add(i).read(), 4); 864 | } 865 | } 866 | igvm_free_buffer(igvm, data); 867 | 868 | igvm_free(igvm); 869 | } 870 | 871 | #[test] 872 | fn c_api_test_no_associated_data() { 873 | let file_data = create_igvm(); 874 | // SAFETY: The function call requires the size of the buffer to be at least as 875 | // large as the specified length. We are passing a pointer to a vector along with 876 | // the length of the vector so the safety requirements are met. 877 | let igvm = unsafe { igvm_new_from_binary(file_data.as_ptr(), file_data.len() as u32) }; 878 | 879 | let data = igvm_get_header_data(igvm, IgvmHeaderSection::HEADER_SECTION_DIRECTIVE, 9); 880 | assert!(data < 0); 881 | assert_eq!(data, IgvmResult::IGVMAPI_NO_DATA.0); 882 | 883 | igvm_free(igvm); 884 | } 885 | } 886 | -------------------------------------------------------------------------------- /igvm/src/hv_defs.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) Microsoft Corporation. 4 | 5 | //! A subset of the Microsoft hypervisor definitions used by the igvm crate. 6 | //! 7 | //! These types are defined in the Microsoft Hypervisor Top Level Funtional 8 | //! Specification (TLFS), which can be found 9 | //! [here](https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/tlfs). 10 | 11 | use core::fmt::Debug; 12 | use open_enum::open_enum; 13 | use zerocopy::FromBytes; 14 | use zerocopy::Immutable; 15 | use zerocopy::IntoBytes; 16 | use zerocopy::KnownLayout; 17 | 18 | #[open_enum] 19 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 20 | #[repr(u16)] 21 | pub enum HvError { 22 | InvalidHypercallCode = 0x0002, 23 | InvalidHypercallInput = 0x0003, 24 | InvalidAlignment = 0x0004, 25 | InvalidParameter = 0x0005, 26 | AccessDenied = 0x0006, 27 | InvalidPartitionState = 0x0007, 28 | OperationDenied = 0x0008, 29 | UnknownProperty = 0x0009, 30 | PropertyValueOutOfRange = 0x000A, 31 | InsufficientMemory = 0x000B, 32 | PartitionTooDeep = 0x000C, 33 | InvalidPartitionId = 0x000D, 34 | InvalidVpIndex = 0x000E, 35 | NotFound = 0x0010, 36 | InvalidPortId = 0x0011, 37 | InvalidConnectionId = 0x0012, 38 | InsufficientBuffers = 0x0013, 39 | NotAcknowledged = 0x0014, 40 | InvalidVpState = 0x0015, 41 | Acknowledged = 0x0016, 42 | InvalidSaveRestoreState = 0x0017, 43 | InvalidSynicState = 0x0018, 44 | ObjectInUse = 0x0019, 45 | InvalidProximityDomainInfo = 0x001A, 46 | NoData = 0x001B, 47 | Inactive = 0x001C, 48 | NoResources = 0x001D, 49 | FeatureUnavailable = 0x001E, 50 | PartialPacket = 0x001F, 51 | ProcessorFeatureNotSupported = 0x0020, 52 | ProcessorCacheLineFlushSizeIncompatible = 0x0030, 53 | InsufficientBuffer = 0x0033, 54 | IncompatibleProcessor = 0x0037, 55 | InsufficientDeviceDomains = 0x0038, 56 | CpuidFeatureValidationError = 0x003C, 57 | CpuidXsaveFeatureValidationError = 0x003D, 58 | ProcessorStartupTimeout = 0x003E, 59 | SmxEnabled = 0x003F, 60 | InvalidLpIndex = 0x0041, 61 | InvalidRegisterValue = 0x0050, 62 | InvalidVtlState = 0x0051, 63 | NxNotDetected = 0x0055, 64 | InvalidDeviceId = 0x0057, 65 | InvalidDeviceState = 0x0058, 66 | PendingPageRequests = 0x0059, 67 | PageRequestInvalid = 0x0060, 68 | KeyAlreadyExists = 0x0065, 69 | DeviceAlreadyInDomain = 0x0066, 70 | InvalidCpuGroupId = 0x006F, 71 | InvalidCpuGroupState = 0x0070, 72 | OperationFailed = 0x0071, 73 | NotAllowedWithNestedVirtActive = 0x0072, 74 | InsufficientRootMemory = 0x0073, 75 | EventBufferAlreadyFreed = 0x0074, 76 | VtlAlreadyEnabled = 0x0086, 77 | } 78 | 79 | impl core::fmt::Display for HvError { 80 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 81 | let error_str = match *self { 82 | HvError::InvalidHypercallCode => "Invalid hypercall code", 83 | HvError::InvalidHypercallInput => "Invalid hypercall input", 84 | HvError::InvalidAlignment => "Invalid alignment", 85 | HvError::InvalidParameter => "Invalid parameter", 86 | HvError::AccessDenied => "Access denied", 87 | HvError::InvalidPartitionState => "Invalid partition state", 88 | HvError::OperationDenied => "Operation denied", 89 | HvError::UnknownProperty => "Unknown property", 90 | HvError::PropertyValueOutOfRange => "Property value out of range", 91 | HvError::InsufficientMemory => "Insufficient memory", 92 | HvError::PartitionTooDeep => "Partition too deep", 93 | HvError::InvalidPartitionId => "Invalid partition ID", 94 | HvError::InvalidVpIndex => "Invalid VP index", 95 | HvError::NotFound => "Not found", 96 | HvError::InvalidPortId => "Invalid port ID", 97 | HvError::InvalidConnectionId => "Invalid connection ID", 98 | HvError::InsufficientBuffers => "Insufficient buffers", 99 | HvError::NotAcknowledged => "Not acknowledged", 100 | HvError::InvalidVpState => "Invalid VP state", 101 | HvError::Acknowledged => "Acknowledged", 102 | HvError::InvalidSaveRestoreState => "Invalid save restore state", 103 | HvError::InvalidSynicState => "Invalid SynIC state", 104 | HvError::ObjectInUse => "Object in use", 105 | HvError::InvalidProximityDomainInfo => "Invalid proximity domain info", 106 | HvError::NoData => "No data", 107 | HvError::Inactive => "Inactive", 108 | HvError::NoResources => "No resources", 109 | HvError::FeatureUnavailable => "Feature unavailable", 110 | HvError::PartialPacket => "Partial packet", 111 | HvError::ProcessorFeatureNotSupported => "Processor feature not supported", 112 | HvError::ProcessorCacheLineFlushSizeIncompatible => { 113 | "Processor cache line flush size incompatible" 114 | } 115 | HvError::InsufficientBuffer => "Insufficient buffer", 116 | HvError::IncompatibleProcessor => "Incompatible processor", 117 | HvError::InsufficientDeviceDomains => "Insufficient device domains", 118 | HvError::CpuidFeatureValidationError => "CPUID feature validation error", 119 | HvError::CpuidXsaveFeatureValidationError => "CPUID XSAVE feature validation error", 120 | HvError::ProcessorStartupTimeout => "Processor startup timeout", 121 | HvError::SmxEnabled => "SMX enabled", 122 | HvError::InvalidLpIndex => "Invalid LP index", 123 | HvError::InvalidRegisterValue => "Invalid register value", 124 | HvError::InvalidVtlState => "Invalid VTL state", 125 | HvError::NxNotDetected => "NX not detected", 126 | HvError::InvalidDeviceId => "Invalid device ID", 127 | HvError::InvalidDeviceState => "Invalid device state", 128 | HvError::PendingPageRequests => "Pending page requests", 129 | HvError::PageRequestInvalid => "Page request invalid", 130 | HvError::KeyAlreadyExists => "Key already exists", 131 | HvError::DeviceAlreadyInDomain => "Device already in domain", 132 | HvError::InvalidCpuGroupId => "Invalid CPU group ID", 133 | HvError::InvalidCpuGroupState => "Invalid CPU group state", 134 | HvError::OperationFailed => "Operation failed", 135 | HvError::NotAllowedWithNestedVirtActive => { 136 | "Not allowed with nested virtualization active" 137 | } 138 | HvError::InsufficientRootMemory => "Insufficient root memory", 139 | HvError::EventBufferAlreadyFreed => "Event buffer already freed", 140 | other => return write!(f, "Hypervisor error {:#06x}", other.0), 141 | }; 142 | f.write_str(error_str) 143 | } 144 | } 145 | 146 | impl std::error::Error for HvError {} 147 | 148 | /// A result type with error type [`HvError`]. 149 | pub type HvResult = Result; 150 | 151 | /// A Virtual Trust Level (VTL) defined by Virtual Secure Mode (VSM). 152 | #[repr(u8)] 153 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 154 | pub enum Vtl { 155 | /// VTL0. 156 | Vtl0 = 0, 157 | /// VTL1. 158 | Vtl1 = 1, 159 | /// VTL2. 160 | Vtl2 = 2, 161 | } 162 | 163 | impl TryFrom for Vtl { 164 | type Error = HvError; 165 | 166 | fn try_from(value: u8) -> Result { 167 | Ok(match value { 168 | 0 => Self::Vtl0, 169 | 1 => Self::Vtl1, 170 | 2 => Self::Vtl2, 171 | _ => return Err(HvError::InvalidParameter), 172 | }) 173 | } 174 | } 175 | 176 | impl From for u8 { 177 | fn from(value: Vtl) -> Self { 178 | value as u8 179 | } 180 | } 181 | 182 | /// An aligned u128 value. 183 | #[repr(C, align(16))] 184 | #[derive(Copy, Clone, PartialEq, Eq, IntoBytes, Immutable, KnownLayout, FromBytes)] 185 | pub struct AlignedU128([u8; 16]); 186 | 187 | impl AlignedU128 { 188 | pub fn to_ne_bytes(&self) -> [u8; 16] { 189 | self.0 190 | } 191 | 192 | pub fn from_ne_bytes(val: [u8; 16]) -> Self { 193 | Self(val) 194 | } 195 | } 196 | 197 | impl Debug for AlignedU128 { 198 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 199 | Debug::fmt(&u128::from_ne_bytes(self.0), f) 200 | } 201 | } 202 | 203 | impl From for AlignedU128 { 204 | fn from(v: u128) -> Self { 205 | Self(v.to_ne_bytes()) 206 | } 207 | } 208 | 209 | impl From for AlignedU128 { 210 | fn from(v: u64) -> Self { 211 | (v as u128).into() 212 | } 213 | } 214 | 215 | impl From for AlignedU128 { 216 | fn from(v: u32) -> Self { 217 | (v as u128).into() 218 | } 219 | } 220 | 221 | impl From for AlignedU128 { 222 | fn from(v: u16) -> Self { 223 | (v as u128).into() 224 | } 225 | } 226 | 227 | impl From for AlignedU128 { 228 | fn from(v: u8) -> Self { 229 | (v as u128).into() 230 | } 231 | } 232 | 233 | impl From for u128 { 234 | fn from(v: AlignedU128) -> Self { 235 | u128::from_ne_bytes(v.0) 236 | } 237 | } 238 | 239 | /// A `HV_REGISTER_VALUE` that represents virtual processor registers. 240 | #[repr(C)] 241 | #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoBytes, Immutable, KnownLayout, FromBytes)] 242 | pub struct HvRegisterValue(pub AlignedU128); 243 | 244 | impl HvRegisterValue { 245 | pub fn as_u128(&self) -> u128 { 246 | self.0.into() 247 | } 248 | 249 | pub fn as_u64(&self) -> u64 { 250 | self.as_u128() as u64 251 | } 252 | 253 | pub fn as_u32(&self) -> u32 { 254 | self.as_u128() as u32 255 | } 256 | 257 | pub fn as_u16(&self) -> u16 { 258 | self.as_u128() as u16 259 | } 260 | 261 | pub fn as_u8(&self) -> u8 { 262 | self.as_u128() as u8 263 | } 264 | 265 | pub fn as_table(&self) -> HvX64TableRegister { 266 | HvX64TableRegister::read_from_bytes(self.as_bytes()).unwrap() 267 | } 268 | 269 | pub fn as_segment(&self) -> HvX64SegmentRegister { 270 | HvX64SegmentRegister::read_from_bytes(self.as_bytes()).unwrap() 271 | } 272 | } 273 | 274 | impl From for HvRegisterValue { 275 | fn from(val: u8) -> Self { 276 | (val as u128).into() 277 | } 278 | } 279 | 280 | impl From for HvRegisterValue { 281 | fn from(val: u16) -> Self { 282 | (val as u128).into() 283 | } 284 | } 285 | 286 | impl From for HvRegisterValue { 287 | fn from(val: u32) -> Self { 288 | (val as u128).into() 289 | } 290 | } 291 | 292 | impl From for HvRegisterValue { 293 | fn from(val: u64) -> Self { 294 | (val as u128).into() 295 | } 296 | } 297 | 298 | impl From for HvRegisterValue { 299 | fn from(val: u128) -> Self { 300 | Self(val.into()) 301 | } 302 | } 303 | 304 | #[repr(C)] 305 | #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoBytes, Immutable, KnownLayout, FromBytes)] 306 | pub struct HvX64TableRegister { 307 | pub pad: [u16; 3], 308 | pub limit: u16, 309 | pub base: u64, 310 | } 311 | static_assertions::const_assert_eq!(core::mem::size_of::(), 16); 312 | 313 | impl From for HvRegisterValue { 314 | fn from(val: HvX64TableRegister) -> Self { 315 | Self::read_from_bytes(val.as_bytes()).unwrap() 316 | } 317 | } 318 | 319 | impl From for HvX64TableRegister { 320 | fn from(val: HvRegisterValue) -> Self { 321 | Self::read_from_bytes(val.as_bytes()).unwrap() 322 | } 323 | } 324 | 325 | #[repr(C)] 326 | #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoBytes, Immutable, KnownLayout, FromBytes)] 327 | pub struct HvX64SegmentRegister { 328 | pub base: u64, 329 | pub limit: u32, 330 | pub selector: u16, 331 | pub attributes: u16, 332 | } 333 | static_assertions::const_assert_eq!(core::mem::size_of::(), 16); 334 | 335 | impl From for HvRegisterValue { 336 | fn from(val: HvX64SegmentRegister) -> Self { 337 | Self::read_from_bytes(val.as_bytes()).unwrap() 338 | } 339 | } 340 | 341 | impl From for HvX64SegmentRegister { 342 | fn from(val: HvRegisterValue) -> Self { 343 | Self::read_from_bytes(val.as_bytes()).unwrap() 344 | } 345 | } 346 | 347 | macro_rules! registers { 348 | ($name:ident { 349 | $( 350 | $(#[$vattr:meta])* 351 | $variant:ident = $value:expr 352 | ),* 353 | $(,)? 354 | }) => { 355 | #[open_enum] 356 | #[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Debug, Clone, Copy, PartialEq, Eq)] 357 | #[repr(u32)] 358 | pub enum $name { 359 | $($variant = $value,)* 360 | InstructionEmulationHints = 0x00000002, 361 | InternalActivityState = 0x00000004, 362 | 363 | // Guest Crash Registers 364 | GuestCrashP0 = 0x00000210, 365 | GuestCrashP1 = 0x00000211, 366 | GuestCrashP2 = 0x00000212, 367 | GuestCrashP3 = 0x00000213, 368 | GuestCrashP4 = 0x00000214, 369 | GuestCrashCtl = 0x00000215, 370 | 371 | PendingInterruption = 0x00010002, 372 | InterruptState = 0x00010003, 373 | PendingEvent0 = 0x00010004, 374 | PendingEvent1 = 0x00010005, 375 | 376 | VpRuntime = 0x00090000, 377 | GuestOsId = 0x00090002, 378 | VpIndex = 0x00090003, 379 | TimeRefCount = 0x00090004, 380 | CpuManagementVersion = 0x00090007, 381 | VpAssistPage = 0x00090013, 382 | VpRootSignalCount = 0x00090014, 383 | ReferenceTsc = 0x00090017, 384 | VpConfig = 0x00090018, 385 | Ghcb = 0x00090019, 386 | ReferenceTscSequence = 0x0009001A, 387 | GuestSchedulerEvent = 0x0009001B, 388 | 389 | Sint0 = 0x000A0000, 390 | Sint1 = 0x000A0001, 391 | Sint2 = 0x000A0002, 392 | Sint3 = 0x000A0003, 393 | Sint4 = 0x000A0004, 394 | Sint5 = 0x000A0005, 395 | Sint6 = 0x000A0006, 396 | Sint7 = 0x000A0007, 397 | Sint8 = 0x000A0008, 398 | Sint9 = 0x000A0009, 399 | Sint10 = 0x000A000A, 400 | Sint11 = 0x000A000B, 401 | Sint12 = 0x000A000C, 402 | Sint13 = 0x000A000D, 403 | Sint14 = 0x000A000E, 404 | Sint15 = 0x000A000F, 405 | Scontrol = 0x000A0010, 406 | Sversion = 0x000A0011, 407 | Sifp = 0x000A0012, 408 | Sipp = 0x000A0013, 409 | Eom = 0x000A0014, 410 | Sirbp = 0x000A0015, 411 | 412 | VsmCodePageOffsets = 0x000D0002, 413 | VsmVpStatus = 0x000D0003, 414 | VsmPartitionStatus = 0x000D0004, 415 | VsmVina = 0x000D0005, 416 | VsmCapabilities = 0x000D0006, 417 | VsmPartitionConfig = 0x000D0007, 418 | GuestVsmPartitionConfig = 0x000D0008, 419 | VsmVpSecureConfigVtl0 = 0x000D0010, 420 | VsmVpSecureConfigVtl1 = 0x000D0011, 421 | VsmVpSecureConfigVtl2 = 0x000D0012, 422 | VsmVpSecureConfigVtl3 = 0x000D0013, 423 | VsmVpSecureConfigVtl4 = 0x000D0014, 424 | VsmVpSecureConfigVtl5 = 0x000D0015, 425 | VsmVpSecureConfigVtl6 = 0x000D0016, 426 | VsmVpSecureConfigVtl7 = 0x000D0017, 427 | VsmVpSecureConfigVtl8 = 0x000D0018, 428 | VsmVpSecureConfigVtl9 = 0x000D0019, 429 | VsmVpSecureConfigVtl10 = 0x000D001A, 430 | VsmVpSecureConfigVtl11 = 0x000D001B, 431 | VsmVpSecureConfigVtl12 = 0x000D001C, 432 | VsmVpSecureConfigVtl13 = 0x000D001D, 433 | VsmVpSecureConfigVtl14 = 0x000D001E, 434 | VsmVpWaitForTlbLock = 0x000D0020, 435 | } 436 | }; 437 | } 438 | 439 | registers! { 440 | HvX64RegisterName { 441 | DeliverabilityNotifications = 0x00010006, 442 | 443 | // X64 User-Mode Registers 444 | Rax = 0x00020000, 445 | Rcx = 0x00020001, 446 | Rdx = 0x00020002, 447 | Rbx = 0x00020003, 448 | Rsp = 0x00020004, 449 | Rbp = 0x00020005, 450 | Rsi = 0x00020006, 451 | Rdi = 0x00020007, 452 | R8 = 0x00020008, 453 | R9 = 0x00020009, 454 | R10 = 0x0002000a, 455 | R11 = 0x0002000b, 456 | R12 = 0x0002000c, 457 | R13 = 0x0002000d, 458 | R14 = 0x0002000e, 459 | R15 = 0x0002000f, 460 | Rip = 0x00020010, 461 | Rflags = 0x00020011, 462 | 463 | // X64 Floating Point and Vector Registers 464 | Xmm0 = 0x00030000, 465 | Xmm1 = 0x00030001, 466 | Xmm2 = 0x00030002, 467 | Xmm3 = 0x00030003, 468 | Xmm4 = 0x00030004, 469 | Xmm5 = 0x00030005, 470 | Xmm6 = 0x00030006, 471 | Xmm7 = 0x00030007, 472 | Xmm8 = 0x00030008, 473 | Xmm9 = 0x00030009, 474 | Xmm10 = 0x0003000A, 475 | Xmm11 = 0x0003000B, 476 | Xmm12 = 0x0003000C, 477 | Xmm13 = 0x0003000D, 478 | Xmm14 = 0x0003000E, 479 | Xmm15 = 0x0003000F, 480 | FpMmx0 = 0x00030010, 481 | FpMmx1 = 0x00030011, 482 | FpMmx2 = 0x00030012, 483 | FpMmx3 = 0x00030013, 484 | FpMmx4 = 0x00030014, 485 | FpMmx5 = 0x00030015, 486 | FpMmx6 = 0x00030016, 487 | FpMmx7 = 0x00030017, 488 | FpControlStatus = 0x00030018, 489 | XmmControlStatus = 0x00030019, 490 | 491 | // X64 Control Registers 492 | Cr0 = 0x00040000, 493 | Cr2 = 0x00040001, 494 | Cr3 = 0x00040002, 495 | Cr4 = 0x00040003, 496 | Cr8 = 0x00040004, 497 | Xfem = 0x00040005, 498 | // X64 Intermediate Control Registers 499 | IntermediateCr0 = 0x00041000, 500 | IntermediateCr4 = 0x00041003, 501 | IntermediateCr8 = 0x00041004, 502 | // X64 Debug Registers 503 | Dr0 = 0x00050000, 504 | Dr1 = 0x00050001, 505 | Dr2 = 0x00050002, 506 | Dr3 = 0x00050003, 507 | Dr6 = 0x00050004, 508 | Dr7 = 0x00050005, 509 | // X64 Segment Registers 510 | Es = 0x00060000, 511 | Cs = 0x00060001, 512 | Ss = 0x00060002, 513 | Ds = 0x00060003, 514 | Fs = 0x00060004, 515 | Gs = 0x00060005, 516 | Ldtr = 0x00060006, 517 | Tr = 0x00060007, 518 | // X64 Table Registers 519 | Idtr = 0x00070000, 520 | Gdtr = 0x00070001, 521 | // X64 Virtualized MSRs 522 | Tsc = 0x00080000, 523 | Efer = 0x00080001, 524 | KernelGsBase = 0x00080002, 525 | ApicBase = 0x00080003, 526 | Pat = 0x00080004, 527 | SysenterCs = 0x00080005, 528 | SysenterEip = 0x00080006, 529 | SysenterEsp = 0x00080007, 530 | Star = 0x00080008, 531 | Lstar = 0x00080009, 532 | Cstar = 0x0008000a, 533 | Sfmask = 0x0008000b, 534 | InitialApicId = 0x0008000c, 535 | // X64 Cache control MSRs 536 | MsrMtrrCap = 0x0008000d, 537 | MsrMtrrDefType = 0x0008000e, 538 | MsrMtrrPhysBase0 = 0x00080010, 539 | MsrMtrrPhysBase1 = 0x00080011, 540 | MsrMtrrPhysBase2 = 0x00080012, 541 | MsrMtrrPhysBase3 = 0x00080013, 542 | MsrMtrrPhysBase4 = 0x00080014, 543 | MsrMtrrPhysBase5 = 0x00080015, 544 | MsrMtrrPhysBase6 = 0x00080016, 545 | MsrMtrrPhysBase7 = 0x00080017, 546 | MsrMtrrPhysBase8 = 0x00080018, 547 | MsrMtrrPhysBase9 = 0x00080019, 548 | MsrMtrrPhysBaseA = 0x0008001a, 549 | MsrMtrrPhysBaseB = 0x0008001b, 550 | MsrMtrrPhysBaseC = 0x0008001c, 551 | MsrMtrrPhysBaseD = 0x0008001d, 552 | MsrMtrrPhysBaseE = 0x0008001e, 553 | MsrMtrrPhysBaseF = 0x0008001f, 554 | MsrMtrrPhysMask0 = 0x00080040, 555 | MsrMtrrPhysMask1 = 0x00080041, 556 | MsrMtrrPhysMask2 = 0x00080042, 557 | MsrMtrrPhysMask3 = 0x00080043, 558 | MsrMtrrPhysMask4 = 0x00080044, 559 | MsrMtrrPhysMask5 = 0x00080045, 560 | MsrMtrrPhysMask6 = 0x00080046, 561 | MsrMtrrPhysMask7 = 0x00080047, 562 | MsrMtrrPhysMask8 = 0x00080048, 563 | MsrMtrrPhysMask9 = 0x00080049, 564 | MsrMtrrPhysMaskA = 0x0008004a, 565 | MsrMtrrPhysMaskB = 0x0008004b, 566 | MsrMtrrPhysMaskC = 0x0008004c, 567 | MsrMtrrPhysMaskD = 0x0008004d, 568 | MsrMtrrPhysMaskE = 0x0008004e, 569 | MsrMtrrPhysMaskF = 0x0008004f, 570 | MsrMtrrFix64k00000 = 0x00080070, 571 | MsrMtrrFix16k80000 = 0x00080071, 572 | MsrMtrrFix16kA0000 = 0x00080072, 573 | MsrMtrrFix4kC0000 = 0x00080073, 574 | MsrMtrrFix4kC8000 = 0x00080074, 575 | MsrMtrrFix4kD0000 = 0x00080075, 576 | MsrMtrrFix4kD8000 = 0x00080076, 577 | MsrMtrrFix4kE0000 = 0x00080077, 578 | MsrMtrrFix4kE8000 = 0x00080078, 579 | MsrMtrrFix4kF0000 = 0x00080079, 580 | MsrMtrrFix4kF8000 = 0x0008007a, 581 | 582 | TscAux = 0x0008007B, 583 | Bndcfgs = 0x0008007C, 584 | DebugCtl = 0x0008007D, 585 | MCount = 0x0008007E, 586 | ACount = 0x0008007F, 587 | 588 | SgxLaunchControl0 = 0x00080080, 589 | SgxLaunchControl1 = 0x00080081, 590 | SgxLaunchControl2 = 0x00080082, 591 | SgxLaunchControl3 = 0x00080083, 592 | SpecCtrl = 0x00080084, 593 | PredCmd = 0x00080085, 594 | VirtSpecCtrl = 0x00080086, 595 | TscVirtualOffset = 0x00080087, 596 | TsxCtrl = 0x00080088, 597 | MsrMcUpdatePatchLevel = 0x00080089, 598 | Available1 = 0x0008008A, 599 | Xss = 0x0008008B, 600 | UCet = 0x0008008C, 601 | SCet = 0x0008008D, 602 | Ssp = 0x0008008E, 603 | Pl0Ssp = 0x0008008F, 604 | Pl1Ssp = 0x00080090, 605 | Pl2Ssp = 0x00080091, 606 | Pl3Ssp = 0x00080092, 607 | InterruptSspTableAddr = 0x00080093, 608 | TscVirtualMultiplier = 0x00080094, 609 | TscDeadline = 0x00080095, 610 | TscAdjust = 0x00080096, 611 | Pasid = 0x00080097, 612 | UmwaitControl = 0x00080098, 613 | Xfd = 0x00080099, 614 | XfdErr = 0x0008009A, 615 | 616 | Hypercall = 0x00090001, 617 | 618 | // Partition Timer Assist Registers 619 | EmulatedTimerPeriod = 0x00090030, 620 | EmulatedTimerControl = 0x00090031, 621 | PmTimerAssist = 0x00090032, 622 | } 623 | } 624 | 625 | registers! { 626 | HvArm64RegisterName { 627 | X0 = 0x00020000, 628 | X1 = 0x00020001, 629 | X2 = 0x00020002, 630 | X3 = 0x00020003, 631 | X4 = 0x00020004, 632 | X5 = 0x00020005, 633 | X6 = 0x00020006, 634 | X7 = 0x00020007, 635 | X8 = 0x00020008, 636 | X9 = 0x00020009, 637 | X10 = 0x0002000A, 638 | X11 = 0x0002000B, 639 | X12 = 0x0002000C, 640 | X13 = 0x0002000D, 641 | X14 = 0x0002000E, 642 | X15 = 0x0002000F, 643 | X16 = 0x00020010, 644 | X17 = 0x00020011, 645 | X18 = 0x00020012, 646 | X19 = 0x00020013, 647 | X20 = 0x00020014, 648 | X21 = 0x00020015, 649 | X22 = 0x00020016, 650 | X23 = 0x00020017, 651 | X24 = 0x00020018, 652 | X25 = 0x00020019, 653 | X26 = 0x0002001A, 654 | X27 = 0x0002001B, 655 | X28 = 0x0002001C, 656 | XFp = 0x0002001D, 657 | XLr = 0x0002001E, 658 | XSp = 0x0002001F, // alias for either El0/x depending on Cpsr.SPSel 659 | XSpEl0 = 0x00020020, 660 | XSpElx = 0x00020021, 661 | XPc = 0x00020022, 662 | Cpsr = 0x00020023, 663 | SctlrEl1 = 0x00040002, 664 | Ttbr0El1 = 0x00040005, 665 | Ttbr1El1 = 0x00040006, 666 | TcrEl1 = 0x00040007, 667 | EsrEl1 = 0x00040008, 668 | FarEl1 = 0x00040009, 669 | ElrEl1 = 0x00040015, 670 | MairEl1 = 0x0004000B, 671 | VbarEl1 = 0x0004000C, 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /igvm/src/page_table.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) Microsoft Corporation. 4 | 5 | //! Methods to construct page tables. 6 | 7 | use crate::hv_defs::Vtl; 8 | use range_map_vec::RangeMap; 9 | use std::collections::BTreeMap; 10 | use thiserror::Error; 11 | use zerocopy::FromBytes; 12 | use zerocopy::FromZeros; 13 | use zerocopy::Immutable; 14 | use zerocopy::IntoBytes; 15 | use zerocopy::KnownLayout; 16 | use zerocopy::Unalign; 17 | 18 | const X64_CR4_LA57: u64 = 0x0000000000001000; // 5-level paging enabled 19 | 20 | const X64_PTE_PRESENT: u64 = 1; 21 | const X64_PTE_READ_WRITE: u64 = 1 << 1; 22 | const X64_PTE_ACCESSED: u64 = 1 << 5; 23 | const X64_PTE_DIRTY: u64 = 1 << 6; 24 | const X64_PTE_LARGE_PAGE: u64 = 1 << 7; 25 | 26 | const PAGE_TABLE_ENTRY_COUNT: usize = 512; 27 | 28 | const X64_PAGE_SHIFT: u64 = 12; 29 | const X64_PTE_BITS: u64 = 9; 30 | 31 | /// Number of bytes in a page for X64. 32 | pub const X64_PAGE_SIZE: u64 = 4096; 33 | 34 | /// Number of bytes in a large page for X64. 35 | pub const X64_LARGE_PAGE_SIZE: u64 = 0x200000; 36 | 37 | /// Number of bytes in a 1GB page for X64. 38 | pub const X64_1GB_PAGE_SIZE: u64 = 0x40000000; 39 | 40 | #[derive(Copy, Clone, PartialEq, Eq, IntoBytes, Immutable, KnownLayout, FromBytes)] 41 | #[repr(transparent)] 42 | pub struct PageTableEntry { 43 | entry: u64, 44 | } 45 | 46 | impl std::fmt::Debug for PageTableEntry { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | f.debug_struct("PageTableEntry") 49 | .field("entry", &self.entry) 50 | .field("is_present", &self.is_present()) 51 | .field("is_large_page", &self.is_large_page()) 52 | .field("gpa", &self.gpa()) 53 | .finish() 54 | } 55 | } 56 | 57 | #[derive(Debug, Copy, Clone)] 58 | pub enum PageTableEntryType { 59 | Leaf1GbPage(u64), 60 | Leaf2MbPage(u64), 61 | Leaf4kPage(u64), 62 | Pde(u64), 63 | } 64 | 65 | impl PageTableEntry { 66 | /// Set an AMD64 PDE to either represent a leaf 2MB page or PDE. 67 | /// This sets the PTE to preset, accessed, dirty, read write execute. 68 | pub fn set_entry(&mut self, entry_type: PageTableEntryType) { 69 | self.entry = X64_PTE_PRESENT | X64_PTE_ACCESSED | X64_PTE_READ_WRITE; 70 | 71 | match entry_type { 72 | PageTableEntryType::Leaf1GbPage(address) => { 73 | // Must be 1GB aligned. 74 | assert!(address % X64_1GB_PAGE_SIZE == 0); 75 | self.entry |= address; 76 | self.entry |= X64_PTE_LARGE_PAGE | X64_PTE_DIRTY; 77 | } 78 | PageTableEntryType::Leaf2MbPage(address) => { 79 | // Leaf entry, set like UEFI does for 2MB pages. Must be 2MB aligned. 80 | assert!(address % X64_LARGE_PAGE_SIZE == 0); 81 | self.entry |= address; 82 | self.entry |= X64_PTE_LARGE_PAGE | X64_PTE_DIRTY; 83 | } 84 | PageTableEntryType::Leaf4kPage(address) => { 85 | // Must be 4K aligned. 86 | assert!(address % X64_PAGE_SIZE == 0); 87 | self.entry |= address; 88 | self.entry |= X64_PTE_DIRTY; 89 | } 90 | PageTableEntryType::Pde(address) => { 91 | // Points to another pagetable. 92 | assert!(address % X64_PAGE_SIZE == 0); 93 | self.entry |= address; 94 | } 95 | } 96 | } 97 | 98 | pub fn is_present(&self) -> bool { 99 | self.entry & X64_PTE_PRESENT == X64_PTE_PRESENT 100 | } 101 | 102 | pub fn is_large_page(&self) -> bool { 103 | self.entry & X64_PTE_LARGE_PAGE == X64_PTE_LARGE_PAGE 104 | } 105 | 106 | pub fn gpa(&self) -> Option { 107 | if self.is_present() { 108 | // bits 51 to 12 describe the gpa of the next page table 109 | Some(self.entry & 0x000f_ffff_ffff_f000) 110 | } else { 111 | None 112 | } 113 | } 114 | 115 | pub fn set_addr(&mut self, addr: u64) { 116 | const VALID_BITS: u64 = 0x000f_ffff_ffff_f000; 117 | assert!(addr & !VALID_BITS == 0); 118 | 119 | // clear addr bits, set new addr 120 | self.entry &= !0x000f_ffff_ffff_f000; 121 | self.entry |= addr; 122 | } 123 | 124 | pub fn clear(&mut self) { 125 | self.entry = 0; 126 | } 127 | } 128 | 129 | #[repr(C)] 130 | #[derive(Debug, Copy, Clone, PartialEq, Eq, IntoBytes, Immutable, KnownLayout, FromBytes)] 131 | pub struct PageTable { 132 | entries: [PageTableEntry; PAGE_TABLE_ENTRY_COUNT], 133 | } 134 | 135 | impl PageTable { 136 | // fn iter(&self) -> impl Iterator { 137 | // self.entries.iter() 138 | // } 139 | 140 | pub fn iter_mut(&mut self) -> impl Iterator { 141 | self.entries.iter_mut() 142 | } 143 | 144 | /// Treat this page table as a page table of a given level, and locate the entry corresponding to a va. 145 | pub fn entry(&mut self, gva: u64, level: u8) -> &mut PageTableEntry { 146 | let index = get_amd64_pte_index(gva, level as u64) as usize; 147 | &mut self.entries[index] 148 | } 149 | } 150 | 151 | impl std::ops::Index for PageTable { 152 | type Output = PageTableEntry; 153 | 154 | fn index(&self, index: usize) -> &Self::Output { 155 | &self.entries[index] 156 | } 157 | } 158 | 159 | impl std::ops::IndexMut for PageTable { 160 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 161 | &mut self.entries[index] 162 | } 163 | } 164 | 165 | /// Get an AMD64 PTE index based on page table level. 166 | fn get_amd64_pte_index(gva: u64, page_map_level: u64) -> u64 { 167 | let index = gva >> (X64_PAGE_SHIFT + page_map_level * X64_PTE_BITS); 168 | index & ((1 << X64_PTE_BITS) - 1) 169 | } 170 | 171 | fn flatten_page_table(page_table: Vec) -> Vec { 172 | let mut flat_tables = Vec::with_capacity(page_table.len() * X64_PAGE_SIZE as usize); 173 | for table in page_table { 174 | flat_tables.extend_from_slice(table.as_bytes()); 175 | } 176 | 177 | flat_tables 178 | } 179 | 180 | /// Errors when building a relocated page table. 181 | #[derive(Debug, Error)] 182 | pub enum Error { 183 | #[error("page data length is not 4k")] 184 | PageDataLength, 185 | #[error("page data gpa not contained within builder region")] 186 | PageDataGpa, 187 | #[error("cr3 is not within the page table region")] 188 | Cr3, 189 | #[error("a relocation offset is not aligned to a page table mapping")] 190 | UnalignedOffset { 191 | va: u64, 192 | page_table_entry_mapping_size: u64, 193 | relocation_offset: i64, 194 | }, 195 | #[error("page table region does not have enough free space to fix up page table")] 196 | NotEnoughFreeSpace, 197 | } 198 | 199 | /// Cpu state related to paging. 200 | #[derive(Debug, Clone, Copy)] 201 | pub struct CpuPagingState { 202 | pub cr3: u64, 203 | pub cr4: u64, 204 | } 205 | 206 | /// A builder class that rebuilds the page table specified by an 207 | /// [`crate::IgvmInitializationHeader::PageTableRelocationRegion`]. 208 | #[derive(Debug, Clone)] 209 | pub struct PageTableRelocationBuilder { 210 | /// Base gpa for the page table region. 211 | pub gpa: u64, 212 | /// Size in bytes of the page table region. 213 | pub size: u64, 214 | /// Used bytes in this page table region. 215 | pub used_size: u64, 216 | page_data: Vec, 217 | /// Vp index for cpu state to be queried when rebuilding the page table. 218 | pub vp_index: u16, 219 | /// Vtl for cpu state to be queried when rebuilding the page table. 220 | pub vtl: Vtl, 221 | } 222 | 223 | impl PageTableRelocationBuilder { 224 | pub fn new(gpa: u64, size: u64, used_size: u64, vp_index: u16, vtl: Vtl) -> Self { 225 | assert!(used_size <= size); 226 | 227 | PageTableRelocationBuilder { 228 | gpa, 229 | size, 230 | used_size, 231 | page_data: vec![0; used_size as usize], 232 | vp_index, 233 | vtl, 234 | } 235 | } 236 | 237 | /// Set pre existing page table data from a 238 | /// [`crate::IgvmDirectiveHeader::PageData`]. `gpa` contains the unrelocated 239 | /// gpa stored within the directive header. 240 | pub fn set_page_data(&mut self, gpa: u64, data: &[u8]) -> Result<(), Error> { 241 | // Empty data is valid, as this would mean a page of zeros. 242 | if data.is_empty() { 243 | return Ok(()); 244 | } 245 | 246 | // data must be 4K if it contains non-zero data. 247 | if data.len() != X64_PAGE_SIZE as usize { 248 | return Err(Error::PageDataLength); 249 | } 250 | 251 | if !self.contains(gpa) { 252 | return Err(Error::PageDataGpa); 253 | } 254 | 255 | let start = (gpa - self.gpa) as usize; 256 | let end = start + X64_PAGE_SIZE as usize; 257 | self.page_data[start..end].copy_from_slice(data); 258 | 259 | Ok(()) 260 | } 261 | 262 | pub fn contains(&self, gpa: u64) -> bool { 263 | let end = self.gpa + self.size; 264 | gpa >= self.gpa && gpa < end 265 | } 266 | 267 | /// Recursively walk the page table and fix any PDE entries based on 268 | /// `table_reloc_offest`. Puts entries in regions that have been relocated 269 | /// that require moving to a different PTE entry `entry_map` and clears the 270 | /// original entry. 271 | fn recurse_fixup( 272 | &self, 273 | table_reloc_offset: i64, 274 | page_tables: &mut Vec, 275 | entry_map: &mut BTreeMap, 276 | relocation_offsets: &RangeMap, 277 | table_index: usize, 278 | level: u8, 279 | mut current_va: u64, 280 | ) -> Result<(), Error> { 281 | let mut entry_index = 0; 282 | 283 | /// Information needed to recursively traverse the next level down page 284 | /// table. 285 | struct NextTableInfo { 286 | table_index: usize, 287 | current_va: u64, 288 | } 289 | 290 | while entry_index < 512 { 291 | let table = &mut page_tables[table_index]; 292 | let mut recurse_table = None; 293 | 294 | // Walk each page table entry that hasn't yet been walked. 295 | for entry in table.iter_mut().skip(entry_index) { 296 | entry_index += 1; 297 | let mapping_size = Self::mapping_size(level); 298 | let entry_va = current_va; 299 | current_va += mapping_size; 300 | 301 | if entry.is_present() { 302 | // First check if this is a PDE entry or not. PDE entries 303 | // require recursing further. 304 | let is_pde_entry = match level { 305 | 3 => true, // PML4E entries are always PDEs 306 | 2 | 1 => !entry.is_large_page(), 307 | 0 => false, 308 | _ => unreachable!(), 309 | }; 310 | 311 | if is_pde_entry { 312 | let old_gpa = entry.gpa().expect("entry is present"); 313 | 314 | // Fixup this PDE entry if it lies within the page table 315 | // region, then calculate the next table_index and 316 | // recurse. 317 | if let Some(index) = self.calculate_page_table_index(old_gpa) { 318 | let new_gpa = Self::relocate_address(old_gpa, table_reloc_offset); 319 | entry.set_addr(new_gpa); 320 | recurse_table = Some(NextTableInfo { 321 | table_index: index, 322 | current_va: entry_va, 323 | }); 324 | break; 325 | } else { 326 | // This PDE entry refers to a page table outside of 327 | // the page table relocation region, leave it as is. 328 | continue; 329 | } 330 | } 331 | 332 | // This entry is a leaf entry that maps address space. 333 | // Determine if the region it maps was relocated outside of 334 | // the region mapped by this leaf entry. 335 | let start = entry_va; 336 | let end = entry_va + mapping_size - 1; 337 | if let Some(offset) = relocation_offsets.get_range(start..=end) { 338 | // Determine if we need to actually move this page table 339 | // entry. If the relocation happened within the mapped 340 | // region, there's no need to move the entry. 341 | if offset.unsigned_abs() < mapping_size { 342 | continue; 343 | } 344 | 345 | let new_va = Self::relocate_address(entry_va, *offset); 346 | 347 | // If the new_va is not aligned to the mapping size, 348 | // this relocation and IGVM file is invalid. Bail out 349 | // now. 350 | if new_va % mapping_size != 0 { 351 | return Err(Error::UnalignedOffset { 352 | va: entry_va, 353 | page_table_entry_mapping_size: mapping_size, 354 | relocation_offset: *offset, 355 | }); 356 | } 357 | 358 | let mut new_entry = *entry; 359 | new_entry.set_addr(new_va); 360 | assert!(entry_map.insert(new_va, (level, new_entry)).is_none()); 361 | 362 | entry.clear(); 363 | } else { 364 | // This entry maps a region that's not relocated, so 365 | // leave it as is. 366 | } 367 | } 368 | } 369 | 370 | match recurse_table { 371 | Some(info) => { 372 | // Recurse to the next page table, to fixup any entries 373 | // there. 374 | self.recurse_fixup( 375 | table_reloc_offset, 376 | page_tables, 377 | entry_map, 378 | relocation_offsets, 379 | info.table_index, 380 | level - 1, 381 | info.current_va, 382 | )?; 383 | } 384 | None => { 385 | // The only condition where we're not recursing is when 386 | // we've processed all entries in this table. 387 | assert!(entry_index == 512); 388 | } 389 | } 390 | } 391 | 392 | Ok(()) 393 | } 394 | 395 | fn relocate_address(addr: u64, offset: i64) -> u64 { 396 | if offset >= 0 { 397 | addr + offset as u64 398 | } else { 399 | addr - (offset as u64) 400 | } 401 | } 402 | 403 | fn mapping_size(level: u8) -> u64 { 404 | const SIZE_512_GB: u64 = 0x8000000000; 405 | match level { 406 | 3 => SIZE_512_GB, 407 | 2 => X64_1GB_PAGE_SIZE, 408 | 1 => X64_LARGE_PAGE_SIZE, 409 | 0 => X64_PAGE_SIZE, 410 | _ => unreachable!(), 411 | } 412 | } 413 | 414 | fn calculate_page_table_index(&self, page_table_gpa: u64) -> Option { 415 | if self.contains(page_table_gpa) { 416 | Some(((page_table_gpa - self.gpa) / X64_PAGE_SIZE) as usize) 417 | } else { 418 | None 419 | } 420 | } 421 | 422 | fn calculate_page_table_addr(region_base_gpa: u64, page_table_index: usize) -> u64 { 423 | region_base_gpa + page_table_index as u64 * X64_PAGE_SIZE 424 | } 425 | 426 | /// Build the fixed up page table with the relocation offset for this page 427 | /// table region, and the relocation offsets used for other ranges to fix up 428 | /// page table entries. 429 | pub fn build( 430 | self, 431 | table_reloc_offset: i64, 432 | relocation_offsets: RangeMap, 433 | paging_state: CpuPagingState, 434 | ) -> Result, Error> { 435 | assert_eq!(self.page_data.len() as u64, self.used_size); 436 | 437 | let CpuPagingState { cr3: old_cr3, cr4 } = paging_state; 438 | 439 | if cr4 & X64_CR4_LA57 == X64_CR4_LA57 { 440 | todo!("handle 5 level paging") 441 | } 442 | 443 | if !self.contains(old_cr3) { 444 | return Err(Error::Cr3); 445 | } 446 | 447 | let mut page_tables = <[Unalign]>::ref_from_bytes(self.page_data.as_slice()) 448 | .expect("page data is a valid list of page tables") 449 | .iter() 450 | .map(|v| v.into_inner()) 451 | .collect(); 452 | 453 | // Map of PTEs to relocate. Maps new_va, (page table level, entry value) 454 | let mut entry_map: BTreeMap = BTreeMap::new(); 455 | 456 | // Walk the page table recursively, and fixup PDE entries while 457 | // bookkeping which entries need to be moved. Entries that need to be 458 | // moved will be fixed in pass 2, as additional page tables may need to 459 | // be allocated. 460 | self.recurse_fixup( 461 | table_reloc_offset, 462 | &mut page_tables, 463 | &mut entry_map, 464 | &relocation_offsets, 465 | self.calculate_page_table_index(old_cr3) 466 | .expect("region must contain cr3"), 467 | 3, 468 | 0, 469 | )?; 470 | 471 | let new_cr3 = Self::relocate_address(old_cr3, table_reloc_offset); 472 | 473 | // Add new tables based on how much additional free space exists in the region. 474 | let free_table_count = (self.size - self.used_size) / X64_PAGE_SIZE; 475 | let mut free_table_index = page_tables.len(); 476 | 477 | for _ in 0..free_table_count { 478 | page_tables.push(PageTable::new_zeroed()); 479 | } 480 | 481 | let page_table_len = page_tables.len(); 482 | 483 | // Pass 2, set all entries that have needed relocation due to mapping 484 | // part of a relocatable region. Create a new instance of the page table 485 | // relocation builder that has the correct relocated region info, which 486 | // is used to calculate page table indices. 487 | let reloc_builder = PageTableRelocationBuilder { 488 | gpa: Self::relocate_address(self.gpa, table_reloc_offset), 489 | page_data: Vec::new(), 490 | ..self 491 | }; 492 | for (gva, (entry_level, new_entry)) in entry_map.iter() { 493 | let mut page_table_gpa = new_cr3; 494 | let mut level = 3; 495 | 496 | loop { 497 | let table_index = reloc_builder 498 | .calculate_page_table_index(page_table_gpa) 499 | .expect("should be part of relocation region"); 500 | let entry = page_tables[table_index].entry(*gva, level); 501 | 502 | if level == *entry_level { 503 | // Allow the entry only if it matches exactly the entry it 504 | // would replace. Warn regardless, as it's odd behavior from 505 | // the loaded IGVM file. 506 | if entry.is_present() { 507 | assert_eq!(*entry, *new_entry); 508 | tracing::warn!( 509 | gva, 510 | "page table entry relocated to an already existing identical entry" 511 | ); 512 | } else { 513 | *entry = *new_entry; 514 | } 515 | 516 | break; 517 | } else { 518 | if entry.is_present() { 519 | page_table_gpa = entry.gpa().expect("entry is present"); 520 | } else { 521 | // Allocate a new page table and link it to this entry. 522 | assert!(level > 0); 523 | 524 | if free_table_index == page_table_len { 525 | return Err(Error::NotEnoughFreeSpace); 526 | } 527 | 528 | let new_table_index = free_table_index; 529 | free_table_index += 1; 530 | let new_table_gpa = 531 | Self::calculate_page_table_addr(reloc_builder.gpa, new_table_index); 532 | entry.set_entry(PageTableEntryType::Pde(new_table_gpa)); 533 | 534 | page_table_gpa = new_table_gpa; 535 | } 536 | 537 | level -= 1; 538 | } 539 | } 540 | } 541 | 542 | // Truncate unused tables. 543 | page_tables.truncate(free_table_index); 544 | 545 | Ok(flatten_page_table(page_tables)) 546 | } 547 | } 548 | 549 | #[cfg(test)] 550 | mod tests { 551 | use super::flatten_page_table; 552 | use super::CpuPagingState; 553 | use super::PageTable; 554 | use super::PageTableEntryType; 555 | use super::PageTableRelocationBuilder; 556 | use super::X64_1GB_PAGE_SIZE; 557 | use super::X64_LARGE_PAGE_SIZE; 558 | use super::X64_PAGE_SIZE; 559 | use crate::hv_defs::Vtl; 560 | use range_map_vec::RangeMap; 561 | use zerocopy::FromBytes; 562 | use zerocopy::FromZeros; 563 | use zerocopy::Unalign; 564 | 565 | #[derive(Debug, Clone)] 566 | struct PteInfo { 567 | va: u64, 568 | value: PageTableEntryType, 569 | } 570 | 571 | fn build_page_table(cr3: u64, size: usize, entries: Vec) -> Vec { 572 | let mut page_tables = vec![PageTable::new_zeroed(); size]; 573 | let mut free_index = 1; 574 | let calculate_page_table_index = 575 | |page_table_gpa| -> usize { ((page_table_gpa - cr3) / X64_PAGE_SIZE) as usize }; 576 | 577 | for PteInfo { va, value } in entries { 578 | let mut page_table_gpa = cr3; 579 | let mut level = 3; 580 | let entry_level = match &value { 581 | PageTableEntryType::Leaf1GbPage(_) => 2, 582 | PageTableEntryType::Leaf2MbPage(_) => 1, 583 | PageTableEntryType::Leaf4kPage(_) => 0, 584 | PageTableEntryType::Pde(_) => 0, // Treat as a 4K PTE, but do not actually map. 585 | }; 586 | 587 | loop { 588 | let table_index = calculate_page_table_index(page_table_gpa); 589 | let entry = page_tables[table_index].entry(va, level); 590 | 591 | if level == entry_level { 592 | if !matches!(value, PageTableEntryType::Pde(_)) { 593 | assert!(!entry.is_present()); 594 | entry.set_entry(value); 595 | } 596 | 597 | break; 598 | } else { 599 | if entry.is_present() { 600 | page_table_gpa = entry.gpa().expect("entry is present"); 601 | } else { 602 | // Allocate a new page table and link it to this entry. 603 | assert!(level > 0); 604 | let new_table_index = free_index; 605 | assert!(new_table_index < size); 606 | free_index += 1; 607 | let new_table_gpa = PageTableRelocationBuilder::calculate_page_table_addr( 608 | cr3, 609 | new_table_index, 610 | ); 611 | entry.set_entry(PageTableEntryType::Pde(new_table_gpa)); 612 | 613 | page_table_gpa = new_table_gpa; 614 | } 615 | 616 | level -= 1; 617 | } 618 | } 619 | } 620 | 621 | // shrink built tables based on how many tables actually used 622 | page_tables.truncate(free_index); 623 | 624 | flatten_page_table(page_tables) 625 | } 626 | 627 | #[test] 628 | fn builder_test_relocation() { 629 | // Create a page table with the following: 630 | // 4K page mappings 0 - 8K. 631 | // 2MB page mapping 2MB - 6MB. 632 | // 1GB page mapping 1GB - 3GB. 633 | // 634 | // Check that relocation creates mappings of: 635 | // 4K reloc from 0 - 4k to 1M - 1M+4K. 636 | // 2MB reloc from 2MB - 4MB to 10MB to 12MB. 637 | // 1GB reloc from 1GB to 2GB to 6GB to 7GB. 638 | let original_entries = vec![ 639 | PteInfo { 640 | va: 0, 641 | value: PageTableEntryType::Leaf4kPage(0), 642 | }, 643 | PteInfo { 644 | va: X64_PAGE_SIZE, 645 | value: PageTableEntryType::Leaf4kPage(X64_PAGE_SIZE), 646 | }, 647 | PteInfo { 648 | va: X64_LARGE_PAGE_SIZE, 649 | value: PageTableEntryType::Leaf2MbPage(X64_LARGE_PAGE_SIZE), 650 | }, 651 | PteInfo { 652 | va: 2 * X64_LARGE_PAGE_SIZE, 653 | value: PageTableEntryType::Leaf2MbPage(2 * X64_LARGE_PAGE_SIZE), 654 | }, 655 | PteInfo { 656 | va: X64_1GB_PAGE_SIZE, 657 | value: PageTableEntryType::Leaf1GbPage(X64_1GB_PAGE_SIZE), 658 | }, 659 | PteInfo { 660 | va: 2 * X64_1GB_PAGE_SIZE, 661 | value: PageTableEntryType::Leaf1GbPage(2 * X64_1GB_PAGE_SIZE), 662 | }, 663 | ]; 664 | let small_reloc = 0x100000; // 1MB 665 | let med_reloc = 0x100000 * 8; // 8MB 666 | let large_reloc = X64_1GB_PAGE_SIZE * 5; // 5GB 667 | let reloc_entries = vec![ 668 | PteInfo { 669 | va: X64_PAGE_SIZE, 670 | value: PageTableEntryType::Leaf4kPage(X64_PAGE_SIZE), 671 | }, 672 | PteInfo { 673 | va: small_reloc, 674 | value: PageTableEntryType::Leaf4kPage(small_reloc), 675 | }, 676 | PteInfo { 677 | va: X64_LARGE_PAGE_SIZE + med_reloc, 678 | value: PageTableEntryType::Leaf2MbPage(X64_LARGE_PAGE_SIZE + med_reloc), 679 | }, 680 | PteInfo { 681 | va: 2 * X64_LARGE_PAGE_SIZE, 682 | value: PageTableEntryType::Leaf2MbPage(2 * X64_LARGE_PAGE_SIZE), 683 | }, 684 | PteInfo { 685 | va: X64_1GB_PAGE_SIZE + large_reloc, 686 | value: PageTableEntryType::Leaf1GbPage(X64_1GB_PAGE_SIZE + large_reloc), 687 | }, 688 | PteInfo { 689 | va: 2 * X64_1GB_PAGE_SIZE, 690 | value: PageTableEntryType::Leaf1GbPage(2 * X64_1GB_PAGE_SIZE), 691 | }, 692 | ]; 693 | let cr3 = 1024 * X64_1GB_PAGE_SIZE; 694 | let original_tables = build_page_table(cr3, 4, original_entries); 695 | let cr3_offset = 1024 * X64_1GB_PAGE_SIZE; 696 | let new_tables = build_page_table(cr3 + cr3_offset, 4, reloc_entries); 697 | 698 | let mut builder = PageTableRelocationBuilder::new( 699 | cr3, 700 | (original_tables.len() * 4) as u64, // test truncate behavior 701 | original_tables.len() as u64, 702 | 0, 703 | Vtl::Vtl0, 704 | ); 705 | 706 | original_tables 707 | .as_slice() 708 | .chunks_exact(X64_PAGE_SIZE as usize) 709 | .enumerate() 710 | .for_each(|(index, chunk)| { 711 | builder 712 | .set_page_data(cr3 + index as u64 * X64_PAGE_SIZE, chunk) 713 | .unwrap() 714 | }); 715 | 716 | let mut reloc_map = RangeMap::new(); 717 | reloc_map.insert(0..=X64_PAGE_SIZE - 1, small_reloc as i64); 718 | reloc_map.insert( 719 | X64_LARGE_PAGE_SIZE..=X64_LARGE_PAGE_SIZE * 2 - 1, 720 | med_reloc as i64, 721 | ); 722 | reloc_map.insert( 723 | X64_1GB_PAGE_SIZE..=X64_1GB_PAGE_SIZE * 2 - 1, 724 | large_reloc as i64, 725 | ); 726 | 727 | let built_tables = builder 728 | .build(cr3_offset as i64, reloc_map, CpuPagingState { cr3, cr4: 0 }) 729 | .unwrap(); 730 | 731 | let expected: Vec = 732 | <[Unalign]>::ref_from_bytes(new_tables.as_slice()) 733 | .expect("page data is a valid list of page tables") 734 | .iter() 735 | .map(|v| v.into_inner()) 736 | .collect(); 737 | 738 | let actual: Vec = 739 | <[Unalign]>::ref_from_bytes(built_tables.as_slice()) 740 | .expect("page data is a valid list of page tables") 741 | .iter() 742 | .map(|v| v.into_inner()) 743 | .collect(); 744 | 745 | assert_eq!(expected.len(), actual.len()); 746 | 747 | compare_page_tables(&expected, &actual); 748 | } 749 | 750 | fn compare_page_tables(left: &[PageTable], right: &[PageTable]) { 751 | for (table_index, (left, right)) in left.iter().zip(right.iter()).enumerate() { 752 | for (pte_index, (left, right)) in 753 | left.entries.iter().zip(right.entries.iter()).enumerate() 754 | { 755 | assert_eq!(left, right, "table {} pte {}", table_index, pte_index); 756 | } 757 | } 758 | } 759 | 760 | #[test] 761 | fn builder_illegal_reloc() { 762 | // Create a page table with the following: 763 | // 4K page mappings 0 - 8K. 764 | // 2MB page mapping 2MB - 6MB. 765 | // 766 | // Supply illegal relocation of 1MB, which fails for 2MB pages. 767 | let original_entries = vec![ 768 | PteInfo { 769 | va: 0, 770 | value: PageTableEntryType::Leaf4kPage(0), 771 | }, 772 | PteInfo { 773 | va: X64_PAGE_SIZE, 774 | value: PageTableEntryType::Leaf4kPage(X64_PAGE_SIZE), 775 | }, 776 | PteInfo { 777 | va: X64_LARGE_PAGE_SIZE, 778 | value: PageTableEntryType::Leaf2MbPage(X64_LARGE_PAGE_SIZE), 779 | }, 780 | PteInfo { 781 | va: 2 * X64_LARGE_PAGE_SIZE, 782 | value: PageTableEntryType::Leaf2MbPage(2 * X64_LARGE_PAGE_SIZE), 783 | }, 784 | ]; 785 | let small_reloc = 0x100000; // 1MB 786 | let med_reloc = 3 * 0x100000; // 3MB 787 | let cr3 = 1024 * X64_1GB_PAGE_SIZE; 788 | let original_tables = build_page_table(cr3, 4, original_entries); 789 | let cr3_offset = 1024 * X64_1GB_PAGE_SIZE; 790 | 791 | let mut builder = PageTableRelocationBuilder::new( 792 | cr3, 793 | (original_tables.len() * 4) as u64, // test truncate behavior 794 | original_tables.len() as u64, 795 | 0, 796 | Vtl::Vtl0, 797 | ); 798 | 799 | original_tables 800 | .as_slice() 801 | .chunks_exact(X64_PAGE_SIZE as usize) 802 | .enumerate() 803 | .for_each(|(index, chunk)| { 804 | builder 805 | .set_page_data(cr3 + index as u64 * X64_PAGE_SIZE, chunk) 806 | .unwrap() 807 | }); 808 | 809 | let mut reloc_map = RangeMap::new(); 810 | reloc_map.insert(0..=X64_PAGE_SIZE - 1, small_reloc as i64); 811 | reloc_map.insert( 812 | X64_LARGE_PAGE_SIZE..=X64_LARGE_PAGE_SIZE * 2 - 1, 813 | med_reloc as i64, 814 | ); 815 | 816 | let built_tables = 817 | builder.build(cr3_offset as i64, reloc_map, CpuPagingState { cr3, cr4: 0 }); 818 | 819 | assert!(built_tables.is_err()); 820 | } 821 | 822 | #[test] 823 | fn builder_test_allocation() { 824 | // test that allocating from free space works correctly 825 | let original_entries = vec![ 826 | PteInfo { 827 | va: 0, 828 | value: PageTableEntryType::Leaf4kPage(0), 829 | }, 830 | PteInfo { 831 | va: X64_LARGE_PAGE_SIZE, 832 | value: PageTableEntryType::Leaf2MbPage(X64_LARGE_PAGE_SIZE), 833 | }, 834 | PteInfo { 835 | va: X64_1GB_PAGE_SIZE, 836 | value: PageTableEntryType::Leaf1GbPage(X64_1GB_PAGE_SIZE), 837 | }, 838 | ]; 839 | let reloc = X64_1GB_PAGE_SIZE * 512; 840 | let reloc_entries = vec![ 841 | PteInfo { 842 | va: 0, 843 | value: PageTableEntryType::Pde(0), 844 | }, 845 | PteInfo { 846 | va: reloc, 847 | value: PageTableEntryType::Leaf4kPage(reloc), 848 | }, 849 | PteInfo { 850 | va: X64_LARGE_PAGE_SIZE + reloc, 851 | value: PageTableEntryType::Leaf2MbPage(X64_LARGE_PAGE_SIZE + reloc), 852 | }, 853 | PteInfo { 854 | va: X64_1GB_PAGE_SIZE + reloc, 855 | value: PageTableEntryType::Leaf1GbPage(X64_1GB_PAGE_SIZE + reloc), 856 | }, 857 | ]; 858 | 859 | let cr3 = 2048 * X64_1GB_PAGE_SIZE; 860 | let original_tables = build_page_table(cr3, 4, original_entries); 861 | let new_tables = build_page_table(cr3, 8, reloc_entries); 862 | 863 | let mut builder = PageTableRelocationBuilder::new( 864 | cr3, 865 | original_tables.len() as u64 * 2, 866 | original_tables.len() as u64, 867 | 0, 868 | Vtl::Vtl0, 869 | ); 870 | 871 | original_tables 872 | .as_slice() 873 | .chunks_exact(X64_PAGE_SIZE as usize) 874 | .enumerate() 875 | .for_each(|(index, chunk)| { 876 | builder 877 | .set_page_data(cr3 + index as u64 * X64_PAGE_SIZE, chunk) 878 | .unwrap() 879 | }); 880 | 881 | let mut reloc_map = RangeMap::new(); 882 | reloc_map.insert(0..=2 * X64_1GB_PAGE_SIZE - 1, reloc as i64); 883 | let built_tables = builder 884 | .build(0, reloc_map, CpuPagingState { cr3, cr4: 0 }) 885 | .unwrap(); 886 | 887 | let expected: Vec = 888 | <[Unalign]>::ref_from_bytes(new_tables.as_slice()) 889 | .expect("page data is a valid list of page tables") 890 | .iter() 891 | .map(|v| v.into_inner()) 892 | .collect(); 893 | 894 | let actual: Vec = 895 | <[Unalign]>::ref_from_bytes(built_tables.as_slice()) 896 | .expect("page data is a valid list of page tables") 897 | .iter() 898 | .map(|v| v.into_inner()) 899 | .collect(); 900 | 901 | compare_page_tables(&expected, &actual); 902 | } 903 | } 904 | -------------------------------------------------------------------------------- /igvm/src/registers.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) Microsoft Corporation. 4 | 5 | //! Register types used by the IGVM file format. 6 | 7 | use crate::hv_defs::AlignedU128; 8 | use crate::hv_defs::HvArm64RegisterName; 9 | use crate::hv_defs::HvRegisterValue; 10 | use crate::hv_defs::HvX64RegisterName; 11 | use crate::hv_defs::HvX64SegmentRegister; 12 | use crate::hv_defs::HvX64TableRegister; 13 | use crate::hv_defs::Vtl; 14 | use igvm_defs::VbsVpContextRegister; 15 | use thiserror::Error; 16 | 17 | /// An x86 Table register, like GDTR. 18 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 19 | pub struct TableRegister { 20 | pub base: u64, 21 | pub limit: u16, 22 | } 23 | 24 | impl From for HvRegisterValue { 25 | fn from(reg: TableRegister) -> Self { 26 | let hv_reg = HvX64TableRegister { 27 | pad: [0; 3], 28 | limit: reg.limit, 29 | base: reg.base, 30 | }; 31 | 32 | hv_reg.into() 33 | } 34 | } 35 | 36 | impl From for TableRegister { 37 | fn from(reg: HvX64TableRegister) -> Self { 38 | Self { 39 | base: reg.base, 40 | limit: reg.limit, 41 | } 42 | } 43 | } 44 | 45 | /// An x86 Segment Register, used for the segment selectors. 46 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 47 | pub struct SegmentRegister { 48 | pub base: u64, 49 | pub limit: u32, 50 | pub selector: u16, 51 | pub attributes: u16, 52 | } 53 | 54 | impl From for HvRegisterValue { 55 | fn from(reg: SegmentRegister) -> Self { 56 | let hv_reg = HvX64SegmentRegister { 57 | base: reg.base, 58 | limit: reg.limit, 59 | selector: reg.selector, 60 | attributes: reg.attributes, 61 | }; 62 | 63 | hv_reg.into() 64 | } 65 | } 66 | 67 | impl From for SegmentRegister { 68 | fn from(reg: HvX64SegmentRegister) -> Self { 69 | Self { 70 | base: reg.base, 71 | limit: reg.limit, 72 | selector: reg.selector, 73 | attributes: reg.attributes, 74 | } 75 | } 76 | } 77 | 78 | /// x86 registers that can be stored in IGVM VP context structures. 79 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 80 | pub enum X86Register { 81 | Gdtr(TableRegister), 82 | Idtr(TableRegister), 83 | Ds(SegmentRegister), 84 | Es(SegmentRegister), 85 | Fs(SegmentRegister), 86 | Gs(SegmentRegister), 87 | Ss(SegmentRegister), 88 | Cs(SegmentRegister), 89 | Tr(SegmentRegister), 90 | Cr0(u64), 91 | Cr3(u64), 92 | Cr4(u64), 93 | Efer(u64), 94 | Pat(u64), 95 | Rbp(u64), 96 | Rip(u64), 97 | Rsi(u64), 98 | Rsp(u64), 99 | R8(u64), 100 | R9(u64), 101 | R10(u64), 102 | R11(u64), 103 | R12(u64), 104 | Rflags(u64), 105 | MtrrDefType(u64), 106 | MtrrPhysBase0(u64), 107 | MtrrPhysMask0(u64), 108 | MtrrPhysBase1(u64), 109 | MtrrPhysMask1(u64), 110 | MtrrPhysBase2(u64), 111 | MtrrPhysMask2(u64), 112 | MtrrPhysBase3(u64), 113 | MtrrPhysMask3(u64), 114 | MtrrPhysBase4(u64), 115 | MtrrPhysMask4(u64), 116 | MtrrFix64k00000(u64), 117 | MtrrFix16k80000(u64), 118 | // We do not currently have a need for the middle fixed MTRRs. 119 | MtrrFix4kE0000(u64), 120 | MtrrFix4kE8000(u64), 121 | MtrrFix4kF0000(u64), 122 | MtrrFix4kF8000(u64), 123 | } 124 | 125 | impl X86Register { 126 | pub fn into_vbs_vp_context_reg(&self, vtl: Vtl) -> VbsVpContextRegister { 127 | let (register_name, register_value): (_, HvRegisterValue) = match *self { 128 | X86Register::Gdtr(reg) => (HvX64RegisterName::Gdtr, reg.into()), 129 | X86Register::Idtr(reg) => (HvX64RegisterName::Idtr, reg.into()), 130 | X86Register::Ds(reg) => (HvX64RegisterName::Ds, reg.into()), 131 | X86Register::Es(reg) => (HvX64RegisterName::Es, reg.into()), 132 | X86Register::Fs(reg) => (HvX64RegisterName::Fs, reg.into()), 133 | X86Register::Gs(reg) => (HvX64RegisterName::Gs, reg.into()), 134 | X86Register::Ss(reg) => (HvX64RegisterName::Ss, reg.into()), 135 | X86Register::Cs(reg) => (HvX64RegisterName::Cs, reg.into()), 136 | X86Register::Tr(reg) => (HvX64RegisterName::Tr, reg.into()), 137 | X86Register::Cr0(reg) => (HvX64RegisterName::Cr0, reg.into()), 138 | X86Register::Cr3(reg) => (HvX64RegisterName::Cr3, reg.into()), 139 | X86Register::Cr4(reg) => (HvX64RegisterName::Cr4, reg.into()), 140 | X86Register::Efer(reg) => (HvX64RegisterName::Efer, reg.into()), 141 | X86Register::Pat(reg) => (HvX64RegisterName::Pat, reg.into()), 142 | X86Register::Rbp(reg) => (HvX64RegisterName::Rbp, reg.into()), 143 | X86Register::Rip(reg) => (HvX64RegisterName::Rip, reg.into()), 144 | X86Register::Rsi(reg) => (HvX64RegisterName::Rsi, reg.into()), 145 | X86Register::Rsp(reg) => (HvX64RegisterName::Rsp, reg.into()), 146 | X86Register::R8(reg) => (HvX64RegisterName::R8, reg.into()), 147 | X86Register::R9(reg) => (HvX64RegisterName::R9, reg.into()), 148 | X86Register::R10(reg) => (HvX64RegisterName::R10, reg.into()), 149 | X86Register::R11(reg) => (HvX64RegisterName::R11, reg.into()), 150 | X86Register::R12(reg) => (HvX64RegisterName::R12, reg.into()), 151 | X86Register::Rflags(reg) => (HvX64RegisterName::Rflags, reg.into()), 152 | X86Register::MtrrDefType(v) => (HvX64RegisterName::MsrMtrrDefType, v.into()), 153 | X86Register::MtrrPhysBase0(v) => (HvX64RegisterName::MsrMtrrPhysBase0, v.into()), 154 | X86Register::MtrrPhysMask0(v) => (HvX64RegisterName::MsrMtrrPhysMask0, v.into()), 155 | X86Register::MtrrPhysBase1(v) => (HvX64RegisterName::MsrMtrrPhysBase1, v.into()), 156 | X86Register::MtrrPhysMask1(v) => (HvX64RegisterName::MsrMtrrPhysMask1, v.into()), 157 | X86Register::MtrrPhysBase2(v) => (HvX64RegisterName::MsrMtrrPhysBase2, v.into()), 158 | X86Register::MtrrPhysMask2(v) => (HvX64RegisterName::MsrMtrrPhysMask2, v.into()), 159 | X86Register::MtrrPhysBase3(v) => (HvX64RegisterName::MsrMtrrPhysBase3, v.into()), 160 | X86Register::MtrrPhysMask3(v) => (HvX64RegisterName::MsrMtrrPhysMask3, v.into()), 161 | X86Register::MtrrPhysBase4(v) => (HvX64RegisterName::MsrMtrrPhysBase4, v.into()), 162 | X86Register::MtrrPhysMask4(v) => (HvX64RegisterName::MsrMtrrPhysMask4, v.into()), 163 | X86Register::MtrrFix64k00000(v) => (HvX64RegisterName::MsrMtrrFix64k00000, v.into()), 164 | X86Register::MtrrFix16k80000(v) => (HvX64RegisterName::MsrMtrrFix16k80000, v.into()), 165 | X86Register::MtrrFix4kE0000(v) => (HvX64RegisterName::MsrMtrrFix4kE0000, v.into()), 166 | X86Register::MtrrFix4kE8000(v) => (HvX64RegisterName::MsrMtrrFix4kE8000, v.into()), 167 | X86Register::MtrrFix4kF0000(v) => (HvX64RegisterName::MsrMtrrFix4kF0000, v.into()), 168 | X86Register::MtrrFix4kF8000(v) => (HvX64RegisterName::MsrMtrrFix4kF8000, v.into()), 169 | }; 170 | 171 | VbsVpContextRegister { 172 | vtl: vtl as u8, 173 | register_name: register_name.0.into(), 174 | mbz: [0; 11], 175 | register_value: register_value.0.to_ne_bytes(), 176 | } 177 | } 178 | } 179 | 180 | /// AArch64 registers that can be stored in IGVM VP context structures. 181 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 182 | pub enum AArch64Register { 183 | Pc(u64), 184 | X0(u64), 185 | X1(u64), 186 | Cpsr(u64), 187 | SctlrEl1(u64), 188 | TcrEl1(u64), 189 | MairEl1(u64), 190 | VbarEl1(u64), 191 | Ttbr0El1(u64), 192 | Ttbr1El1(u64), 193 | } 194 | 195 | impl AArch64Register { 196 | pub fn into_vbs_vp_context_reg(&self, vtl: Vtl) -> VbsVpContextRegister { 197 | let (register_name, register_value): (_, HvRegisterValue) = match *self { 198 | AArch64Register::Pc(reg) => (HvArm64RegisterName::XPc, reg.into()), 199 | AArch64Register::X0(reg) => (HvArm64RegisterName::X0, reg.into()), 200 | AArch64Register::X1(reg) => (HvArm64RegisterName::X1, reg.into()), 201 | AArch64Register::Cpsr(reg) => (HvArm64RegisterName::Cpsr, reg.into()), 202 | AArch64Register::SctlrEl1(reg) => (HvArm64RegisterName::SctlrEl1, reg.into()), 203 | AArch64Register::TcrEl1(reg) => (HvArm64RegisterName::TcrEl1, reg.into()), 204 | AArch64Register::MairEl1(reg) => (HvArm64RegisterName::MairEl1, reg.into()), 205 | AArch64Register::VbarEl1(reg) => (HvArm64RegisterName::VbarEl1, reg.into()), 206 | AArch64Register::Ttbr0El1(reg) => (HvArm64RegisterName::Ttbr0El1, reg.into()), 207 | AArch64Register::Ttbr1El1(reg) => (HvArm64RegisterName::Ttbr1El1, reg.into()), 208 | }; 209 | 210 | VbsVpContextRegister { 211 | vtl: vtl as u8, 212 | register_name: register_name.0.into(), 213 | mbz: [0; 11], 214 | register_value: register_value.0.to_ne_bytes(), 215 | } 216 | } 217 | } 218 | 219 | #[derive(Debug, Error)] 220 | #[error("unsupported register {0:#x?}")] 221 | pub struct UnsupportedRegister(T); 222 | 223 | impl TryFrom for X86Register { 224 | type Error = UnsupportedRegister; 225 | 226 | fn try_from(value: igvm_defs::VbsVpContextRegister) -> Result { 227 | // Copy register_value out to its own field to remove reference to packed unaligned field 228 | let register_value = HvRegisterValue(AlignedU128::from_ne_bytes(value.register_value)); 229 | 230 | let reg = match HvX64RegisterName(value.register_name.get()) { 231 | HvX64RegisterName::Gdtr => Self::Gdtr(register_value.as_table().into()), 232 | HvX64RegisterName::Idtr => Self::Idtr(register_value.as_table().into()), 233 | HvX64RegisterName::Ds => Self::Ds(register_value.as_segment().into()), 234 | HvX64RegisterName::Es => Self::Es(register_value.as_segment().into()), 235 | HvX64RegisterName::Fs => Self::Fs(register_value.as_segment().into()), 236 | HvX64RegisterName::Gs => Self::Gs(register_value.as_segment().into()), 237 | HvX64RegisterName::Ss => Self::Ss(register_value.as_segment().into()), 238 | HvX64RegisterName::Cs => Self::Cs(register_value.as_segment().into()), 239 | HvX64RegisterName::Tr => Self::Tr(register_value.as_segment().into()), 240 | HvX64RegisterName::Cr0 => Self::Cr0(register_value.as_u64()), 241 | HvX64RegisterName::Cr3 => Self::Cr3(register_value.as_u64()), 242 | HvX64RegisterName::Cr4 => Self::Cr4(register_value.as_u64()), 243 | HvX64RegisterName::Efer => Self::Efer(register_value.as_u64()), 244 | HvX64RegisterName::Pat => Self::Pat(register_value.as_u64()), 245 | HvX64RegisterName::Rbp => Self::Rbp(register_value.as_u64()), 246 | HvX64RegisterName::Rip => Self::Rip(register_value.as_u64()), 247 | HvX64RegisterName::Rsi => Self::Rsi(register_value.as_u64()), 248 | HvX64RegisterName::Rsp => Self::Rsp(register_value.as_u64()), 249 | HvX64RegisterName::R8 => Self::R8(register_value.as_u64()), 250 | HvX64RegisterName::R9 => Self::R9(register_value.as_u64()), 251 | HvX64RegisterName::R10 => Self::R10(register_value.as_u64()), 252 | HvX64RegisterName::R11 => Self::R11(register_value.as_u64()), 253 | HvX64RegisterName::R12 => Self::R12(register_value.as_u64()), 254 | HvX64RegisterName::Rflags => Self::Rflags(register_value.as_u64()), 255 | HvX64RegisterName::MsrMtrrDefType => Self::MtrrDefType(register_value.as_u64()), 256 | HvX64RegisterName::MsrMtrrPhysBase0 => Self::MtrrPhysBase0(register_value.as_u64()), 257 | HvX64RegisterName::MsrMtrrPhysMask0 => Self::MtrrPhysMask0(register_value.as_u64()), 258 | HvX64RegisterName::MsrMtrrPhysBase1 => Self::MtrrPhysBase1(register_value.as_u64()), 259 | HvX64RegisterName::MsrMtrrPhysMask1 => Self::MtrrPhysMask1(register_value.as_u64()), 260 | HvX64RegisterName::MsrMtrrPhysBase2 => Self::MtrrPhysBase2(register_value.as_u64()), 261 | HvX64RegisterName::MsrMtrrPhysMask2 => Self::MtrrPhysMask2(register_value.as_u64()), 262 | HvX64RegisterName::MsrMtrrPhysBase3 => Self::MtrrPhysBase3(register_value.as_u64()), 263 | HvX64RegisterName::MsrMtrrPhysMask3 => Self::MtrrPhysMask3(register_value.as_u64()), 264 | HvX64RegisterName::MsrMtrrPhysBase4 => Self::MtrrPhysBase4(register_value.as_u64()), 265 | HvX64RegisterName::MsrMtrrPhysMask4 => Self::MtrrPhysMask4(register_value.as_u64()), 266 | HvX64RegisterName::MsrMtrrFix64k00000 => Self::MtrrFix64k00000(register_value.as_u64()), 267 | HvX64RegisterName::MsrMtrrFix16k80000 => Self::MtrrFix16k80000(register_value.as_u64()), 268 | HvX64RegisterName::MsrMtrrFix4kE0000 => Self::MtrrFix4kE0000(register_value.as_u64()), 269 | HvX64RegisterName::MsrMtrrFix4kE8000 => Self::MtrrFix4kE8000(register_value.as_u64()), 270 | HvX64RegisterName::MsrMtrrFix4kF0000 => Self::MtrrFix4kF0000(register_value.as_u64()), 271 | HvX64RegisterName::MsrMtrrFix4kF8000 => Self::MtrrFix4kF8000(register_value.as_u64()), 272 | other => return Err(UnsupportedRegister(other)), 273 | }; 274 | 275 | Ok(reg) 276 | } 277 | } 278 | 279 | impl TryFrom for AArch64Register { 280 | type Error = UnsupportedRegister; 281 | 282 | fn try_from(value: igvm_defs::VbsVpContextRegister) -> Result { 283 | // Copy register_value out to its own field to remove reference to packed unaligned field 284 | let register_value = HvRegisterValue(AlignedU128::from_ne_bytes(value.register_value)); 285 | 286 | let reg = match HvArm64RegisterName(value.register_name.get()) { 287 | HvArm64RegisterName::XPc => Self::Pc(register_value.as_u64()), 288 | HvArm64RegisterName::X0 => Self::X0(register_value.as_u64()), 289 | HvArm64RegisterName::X1 => Self::X1(register_value.as_u64()), 290 | HvArm64RegisterName::Cpsr => Self::Cpsr(register_value.as_u64()), 291 | HvArm64RegisterName::SctlrEl1 => Self::SctlrEl1(register_value.as_u64()), 292 | HvArm64RegisterName::TcrEl1 => Self::TcrEl1(register_value.as_u64()), 293 | HvArm64RegisterName::MairEl1 => Self::MairEl1(register_value.as_u64()), 294 | HvArm64RegisterName::VbarEl1 => Self::VbarEl1(register_value.as_u64()), 295 | HvArm64RegisterName::Ttbr0El1 => Self::Ttbr0El1(register_value.as_u64()), 296 | HvArm64RegisterName::Ttbr1El1 => Self::Ttbr1El1(register_value.as_u64()), 297 | other => return Err(UnsupportedRegister(other)), 298 | }; 299 | 300 | Ok(reg) 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /igvm/src/snp_defs.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) Microsoft Corporation. 4 | 5 | //! AMD SEV-SNP specific definitions. 6 | 7 | use zerocopy::FromBytes; 8 | use zerocopy::Immutable; 9 | use zerocopy::IntoBytes; 10 | use zerocopy::KnownLayout; 11 | 12 | /// Virtual Event Injection 13 | /// Defined by the following union in C: 14 | /// ```C 15 | /// typedef union _SEV_EVENT_INJECT_INFO 16 | /// { 17 | /// UINT64 AsUINT64; 18 | /// struct 19 | /// { 20 | /// UINT64 Vector:8; 21 | /// UINT64 InterruptionType:3; // Use SEV_INTR_TYPE_* 22 | /// UINT64 DeliverErrorCode:1; 23 | /// UINT64 Reserved1:19; 24 | /// UINT64 Valid:1; 25 | /// UINT64 ErrorCode:32; 26 | /// }; 27 | /// } SEV_EVENT_INJECT_INFO, *PSEV_EVENT_INJECT_INFO; 28 | /// ``` 29 | #[repr(transparent)] 30 | #[derive(Debug, Clone, Copy, IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)] 31 | pub struct SevEventInjectInfo(pub u64); 32 | 33 | /// A X64 selector register. 34 | #[repr(C)] 35 | #[derive(Debug, Clone, Copy, IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)] 36 | pub struct SevSelector { 37 | pub selector: u16, 38 | pub attrib: u16, 39 | pub limit: u32, 40 | pub base: u64, 41 | } 42 | 43 | /// A X64 XMM register. 44 | #[repr(C)] 45 | #[derive(Debug, Clone, Copy, IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)] 46 | pub struct SevXmmRegister { 47 | pub low: u64, 48 | pub high: u64, 49 | } 50 | 51 | /// SEV feature information. 52 | /// Defined by the following union in C: 53 | ///```C 54 | /// union 55 | /// { 56 | /// UINT64 SevFeatures; 57 | /// struct 58 | /// { 59 | /// UINT64 SevFeatureSNP : 1; 60 | /// UINT64 SevFeatureVTOM : 1; 61 | /// UINT64 SevFeatureReflectVC : 1; 62 | /// UINT64 SevFeatureRestrictInjection : 1; 63 | /// UINT64 SevFeatureAlternateInjection : 1; 64 | /// UINT64 SevFeatureDebugSwap : 1; 65 | /// UINT64 SevFeaturePreventHostIBS : 1; 66 | /// UINT64 SevFeatureSNPBTBIsolation : 1; 67 | /// UINT64 SevFeatureVpmlSSS : 1; 68 | /// UINT64 SevFeatureSecureTscEn : 1; 69 | /// UINT64 SevFeatureResrved1 : 4; 70 | /// UINT64 SevFeatureVmsaRegProt : 1; 71 | /// UINT64 SevFeatureSmtProtection : 1; 72 | /// UINT64 SevFeatureResrved2 : 48; 73 | /// }; 74 | /// }; 75 | ///``` 76 | #[bitfield_struct::bitfield(u64)] 77 | #[derive(IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)] 78 | pub struct SevFeatures { 79 | pub snp: bool, 80 | pub vtom: bool, 81 | pub reflect_vc: bool, 82 | pub restrict_injection: bool, 83 | pub alternate_injection: bool, 84 | pub debug_swap: bool, 85 | pub prevent_host_ibs: bool, 86 | pub snp_btb_isolation: bool, 87 | pub vmpl_supervisor_shadow_stack: bool, 88 | pub secure_tsc: bool, 89 | #[bits(4)] 90 | _reserved1: u8, 91 | pub vmsa_reg_protection: bool, 92 | pub smt_protection: bool, 93 | #[bits(48)] 94 | _reserved2: u64, 95 | } 96 | 97 | /// SEV Virtual interrupt control 98 | /// Defined by the following union in C: 99 | ///```C 100 | /// union 101 | /// { 102 | /// UINT64 VIntrCtrl; 103 | /// #define SEV_VINTR_GUEST_BUSY_BIT 63 104 | /// struct 105 | /// { 106 | /// UINT64 VIntrTPR : 8; 107 | /// UINT64 VIntrIRQ : 1; 108 | /// UINT64 VIntrGIF : 1; 109 | /// UINT64 VIntrShadow : 1; 110 | /// UINT64 VIntrReserved1 : 5; 111 | /// UINT64 VIntrPrio : 4; 112 | /// UINT64 VIntrIgnoreTPR : 1; 113 | /// UINT64 VIntrReserved2 : 11; 114 | /// UINT64 VIntrVector : 8; 115 | /// UINT64 VIntrReserved3 : 23; 116 | /// UINT64 VIntrGuestBusy : 1; 117 | /// }; 118 | /// }; 119 | ///``` 120 | #[repr(transparent)] 121 | #[derive(Debug, Clone, Copy, IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)] 122 | pub struct SevVirtualInterruptControl(pub u64); 123 | 124 | /// SEV VMSA structure representing CPU state 125 | #[repr(C)] 126 | #[derive(Debug, Clone, Copy, IntoBytes, Immutable, KnownLayout, FromBytes, PartialEq, Eq)] 127 | pub struct SevVmsa { 128 | // Selector Info 129 | pub es: SevSelector, 130 | pub cs: SevSelector, 131 | pub ss: SevSelector, 132 | pub ds: SevSelector, 133 | pub fs: SevSelector, 134 | pub gs: SevSelector, 135 | 136 | // Descriptor Table Info 137 | pub gdtr: SevSelector, 138 | pub ldtr: SevSelector, 139 | pub idtr: SevSelector, 140 | pub tr: SevSelector, 141 | 142 | // CET 143 | pub pl0_ssp: u64, 144 | pub pl1_ssp: u64, 145 | pub pl2_ssp: u64, 146 | pub pl3_ssp: u64, 147 | pub u_cet: u64, 148 | 149 | // Reserved, MBZ 150 | pub vmsa_reserved1: [u8; 2], 151 | 152 | // Virtual Machine Privilege Level 153 | pub vmpl: u8, 154 | 155 | // CPL 156 | pub cpl: u8, 157 | 158 | // Reserved, MBZ 159 | pub vmsa_reserved2: u32, 160 | 161 | // EFER 162 | pub efer: u64, 163 | 164 | // Reserved, MBZ 165 | pub vmsa_reserved3: [u32; 26], 166 | 167 | // XSS (offset 0x140) 168 | pub xss: u64, 169 | 170 | // Control registers 171 | pub cr4: u64, 172 | pub cr3: u64, 173 | pub cr0: u64, 174 | 175 | // Debug registers 176 | pub dr7: u64, 177 | pub dr6: u64, 178 | 179 | // RFLAGS 180 | pub rflags: u64, 181 | 182 | // RIP 183 | pub rip: u64, 184 | 185 | // Additional saved debug registers 186 | pub dr0: u64, 187 | pub dr1: u64, 188 | pub dr2: u64, 189 | pub dr3: u64, 190 | 191 | // Debug register address masks 192 | pub dr0_addr_mask: u64, 193 | pub dr1_addr_mask: u64, 194 | pub dr2_addr_mask: u64, 195 | pub dr3_addr_mask: u64, 196 | 197 | // Reserved, MBZ 198 | pub vmsa_reserved4: [u64; 3], 199 | 200 | // RSP 201 | pub rsp: u64, 202 | 203 | // CET 204 | pub s_cet: u64, 205 | pub ssp: u64, 206 | pub interrupt_ssp_table_addr: u64, 207 | 208 | // RAX 209 | pub rax: u64, 210 | 211 | // SYSCALL config registers 212 | pub star: u64, 213 | pub lstar: u64, 214 | pub cstar: u64, 215 | pub sfmask: u64, 216 | 217 | // KernelGsBase 218 | pub kernel_gs_base: u64, 219 | 220 | // SYSENTER config registers 221 | pub sysenter_cs: u64, 222 | pub sysenter_esp: u64, 223 | pub sysenter_eip: u64, 224 | 225 | // CR2 226 | pub cr2: u64, 227 | 228 | // Reserved, MBZ 229 | pub vmsa_reserved5: [u64; 4], 230 | 231 | // PAT 232 | pub pat: u64, 233 | 234 | // LBR MSRs 235 | pub dbgctl: u64, 236 | pub last_branch_from_ip: u64, 237 | pub last_branch_to_ip: u64, 238 | pub last_excp_from_ip: u64, 239 | pub last_excp_to_ip: u64, 240 | 241 | // Reserved, MBZ 242 | pub vmsa_reserved6: [u64; 9], 243 | 244 | // Speculation control MSR 245 | pub spec_ctrl: u64, 246 | 247 | // Reserved, MBZ 248 | pub vmsa_reserved7: [u32; 8], 249 | 250 | // GPRs 251 | pub rcx: u64, 252 | pub rdx: u64, 253 | pub rbx: u64, 254 | pub vmsa_reserved8: u64, // MBZ 255 | pub rbp: u64, 256 | pub rsi: u64, 257 | pub rdi: u64, 258 | pub r8: u64, 259 | pub r9: u64, 260 | pub r10: u64, 261 | pub r11: u64, 262 | pub r12: u64, 263 | pub r13: u64, 264 | pub r14: u64, 265 | pub r15: u64, 266 | 267 | // Reserved, MBZ 268 | pub vmsa_reserved9: [u64; 2], 269 | 270 | // Exit information following an automatic #VMEXIT 271 | pub exit_info1: u64, 272 | pub exit_info2: u64, 273 | pub exit_int_info: u64, 274 | 275 | // Software scratch register 276 | pub next_rip: u64, 277 | 278 | // SEV feature information 279 | pub sev_features: SevFeatures, 280 | 281 | // Virtual interrupt control 282 | pub v_intr_cntrl: SevVirtualInterruptControl, 283 | 284 | // Guest exiting error code 285 | pub guest_error_code: u64, 286 | 287 | // Virtual top of memory 288 | pub virtual_tom: u64, 289 | 290 | // TLB control. Writing a zero to PCPU_ID will force a full TLB 291 | // invalidation upon the next entry. 292 | pub tlb_id: u64, 293 | pub pcpu_id: u64, 294 | 295 | // Event injection 296 | pub event_inject: SevEventInjectInfo, 297 | 298 | // XCR0 299 | pub xcr0: u64, 300 | 301 | // X87 state save valid bitmap 302 | pub xsave_valid_bitmap: [u8; 16], 303 | 304 | // X87 save state 305 | pub x87dp: u64, 306 | pub mxcsr: u32, 307 | pub x87_ftw: u16, 308 | pub x87_fsw: u16, 309 | pub x87_fcw: u16, 310 | pub x87_op: u16, 311 | pub x87_ds: u16, 312 | pub x87_cs: u16, 313 | pub x87_rip: u64, 314 | 315 | // NOTE: Zerocopy doesn't support arrays of 80 size yet. Waiting on const generics? 316 | // Split into chunks, as no code uses this field yet. 317 | // x87_registers: [u8; 80], 318 | pub x87_registers1: [u8; 32], 319 | pub x87_registers2: [u8; 32], 320 | pub x87_registers3: [u8; 16], 321 | 322 | // XMM registers 323 | pub xmm_registers: [SevXmmRegister; 16], 324 | 325 | // YMM high registers 326 | pub ymm_registers: [SevXmmRegister; 16], 327 | } 328 | -------------------------------------------------------------------------------- /igvm_c/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT OR Apache-2.0 2 | # 3 | # Copyright (c) 2023 SUSE LLC 4 | # 5 | # Author: Roy Hopkins 6 | 7 | API_DIR:=$(realpath $(shell dirname $(firstword $(MAKEFILE_LIST)))) 8 | IGVM_DIR := $(API_DIR)/.. 9 | TARGET_DIR ?= target_c 10 | 11 | ifdef RELEASE 12 | TARGET_PATH="$(IGVM_DIR)/$(TARGET_DIR)/release" 13 | else 14 | TARGET_PATH="$(IGVM_DIR)/$(TARGET_DIR)/debug" 15 | endif 16 | 17 | PREFIX ?= /usr 18 | DESTDIR ?= 19 | 20 | CARGO=CARGO_TARGET_DIR=$(IGVM_DIR)/$(TARGET_DIR) cargo 21 | 22 | FEATURES = "igvm-c" 23 | 24 | RUST_SOURCE := $(IGVM_DIR)/igvm/src/c_api.rs $(IGVM_DIR)/igvm/src/lib.rs $(IGVM_DIR)/igvm_defs/src/lib.rs 25 | 26 | # Determine igvm crate version from Cargo.toml 27 | VERSION = $(shell grep -oP "(?<=version = \").+(?=\")" $(IGVM_DIR)/igvm/Cargo.toml) 28 | 29 | .PHONY: install 30 | 31 | all: build test 32 | 33 | build: $(API_DIR)/include/igvm.h $(TARGET_PATH)/dump_igvm 34 | 35 | $(TARGET_PATH)/libigvm.a: 36 | $(CARGO) build --features $(FEATURES) $(EXTRA_PARAMS) --manifest-path=$(IGVM_DIR)/igvm/Cargo.toml 37 | 38 | $(TARGET_PATH)/libigvm_defs.rlib: 39 | $(CARGO) build $(EXTRA_PARAMS) --manifest-path=$(IGVM_DIR)/igvm_defs/Cargo.toml 40 | 41 | $(TARGET_PATH)/test_data: 42 | $(CARGO) build $(EXTRA_PARAMS) --manifest-path=$(IGVM_DIR)/igvm_c/test_data/Cargo.toml 43 | 44 | $(API_DIR)/include/igvm.h: $(RUST_SOURCE) 45 | cbindgen -q -c $(API_DIR)/cbindgen_igvm.toml $(IGVM_DIR)/igvm -o "$(API_DIR)/include/igvm.h" 46 | cbindgen -q -c $(API_DIR)/cbindgen_igvm_defs.toml $(IGVM_DIR)/igvm_defs -o "$(API_DIR)/include/igvm_defs.h" 47 | $(API_DIR)/scripts/post_process.sh "$(API_DIR)/include" 48 | 49 | $(TARGET_PATH)/dump_igvm: $(API_DIR)/include/igvm.h $(API_DIR)/sample/dump_igvm.c $(TARGET_PATH)/libigvm.a 50 | cc -g3 -O0 -I $(API_DIR) -L $(TARGET_PATH) -o $@ $^ -ligvm -ldl -pthread -lm -lutil -lrt 51 | 52 | $(TARGET_PATH)/igvm_test: $(API_DIR)/include/igvm.h $(API_DIR)/tests/igvm_test.c $(TARGET_PATH)/libigvm.a 53 | cc -g3 -O0 -I $(API_DIR) -L $(TARGET_PATH) -o $@ $^ -ligvm -lcunit -ldl -pthread -lm -lutil -lrt 54 | 55 | $(TARGET_PATH)/igvm.bin: $(TARGET_PATH)/test_data 56 | $(TARGET_PATH)/test_data $(TARGET_PATH)/igvm.bin 57 | 58 | test: $(TARGET_PATH)/igvm_test $(TARGET_PATH)/igvm.bin 59 | $(TARGET_PATH)/igvm_test $(TARGET_PATH)/igvm.bin 60 | $(CARGO) test --features $(FEATURES) $(EXTRA_PARAMS) --manifest-path=$(IGVM_DIR)/igvm/Cargo.toml 61 | 62 | clean: 63 | $(CARGO) clean $(EXTRA_PARAMS) --manifest-path=$(IGVM_DIR)/igvm/Cargo.toml 64 | $(CARGO) clean $(EXTRA_PARAMS) --manifest-path=$(IGVM_DIR)/igvm_defs/Cargo.toml 65 | rm -f $(API_DIR)/include/igvm.h $(API_DIR)/include/igvm_defs.h $(TARGET_PATH)/dump_igvm $(TARGET_PATH)/test_data $(TARGET_PATH)/igvm.bin 66 | 67 | install: 68 | mkdir -p $(DESTDIR)/$(PREFIX)/include/igvm 69 | mkdir -p $(DESTDIR)/$(PREFIX)/lib64/pkgconfig 70 | install -m 644 $(TARGET_PATH)/libigvm.a $(DESTDIR)/$(PREFIX)/lib64 71 | install -m 644 $(IGVM_DIR)/igvm_c/include/* $(DESTDIR)/$(PREFIX)/include/igvm 72 | mkdir -p $(DESTDIR)/$(PREFIX)/bin/ 73 | install -m 755 $(TARGET_PATH)/dump_igvm $(DESTDIR)/$(PREFIX)/bin/ 74 | VERSION=$(VERSION) PREFIX=$(PREFIX) envsubst '$$VERSION $$PREFIX' \ 75 | < $(IGVM_DIR)/igvm_c/igvm.pc.in \ 76 | > $(DESTDIR)/$(PREFIX)/lib64/pkgconfig/igvm.pc 77 | -------------------------------------------------------------------------------- /igvm_c/README.md: -------------------------------------------------------------------------------- 1 | # IGVM C API 2 | 3 | This folder contains a Makefile project that is used to build a C compatible API 4 | from the igvm crate. The C API provides functions that can be used to parse and 5 | validate a binary IGVM file and provides access to the fixed header and all of 6 | the variable headers and associated file data. 7 | 8 | The C API is generated directly from the rust source files. This includes the 9 | definitions of the structures and enums in igvm_defs. This ensures that the C 10 | API does not need to be manually updated inline with any changes to the rust 11 | definitions. 12 | 13 | ## Dependencies 14 | The C API header files are generated using the `cbindgen` tool. This tool needs 15 | to be installed before the API can be built. This can be achieved using: 16 | 17 | ```bash 18 | cargo install --force cbindgen 19 | ``` 20 | 21 | In addition, `sample/dump_igvm` and the C unit tests requires a C compiler to be 22 | installed. 23 | 24 | The unit tests require CUnit to be installed. 25 | 26 | ## Building 27 | The C API can be built with: 28 | 29 | ```bash 30 | make -f Makefile 31 | ``` 32 | 33 | This builds both the igvm and igvm_defs rust projects enabling the `igvm-c` 34 | feature. In order to keep the C API build separate from the normal build, the 35 | cargo target directory is set to `target_c`. 36 | 37 | The following output files are generated for the build: 38 | 39 | `target_c/[debug | release]/libigvm.a`: Static library that includes the 40 | exported C functions. 41 | 42 | `igvm_c/include/igvm_defs.h`: Definitions of the IGVM structures. 43 | `igvm_c/include/igvm.h`: Declarations of the C API functions. 44 | 45 | The file `igvm.h` includes `igvm_defs.h` so only this file needs to be included 46 | in C projects source files. 47 | 48 | ## Installing 49 | Once built, the library can be installed with: 50 | 51 | ```bash 52 | make -f Makefile install 53 | ``` 54 | 55 | By default, the library will be installed into `/usr` which will require root 56 | privileges. Alternatively, the library can be installed in a different location 57 | by setting `PREFIX`: 58 | 59 | ```bash 60 | PREFIX=/path/to/installation make -f Makefile install 61 | ``` 62 | 63 | ## Sample application 64 | The C API build generates a test application named `dump_igvm`. This application 65 | can take the path of a binary IGVM file as a parameter and will use the C API to 66 | parse the file and dump the contents to the console. 67 | 68 | ## Unit tests 69 | A test executable is built and automatically invoked during the build process. 70 | This performs a number tests to ensure the C API can parse a binary file and 71 | allow access to the fixed header and each variable header type. 72 | 73 | A simple rust project is provided in `igvm_c/test_data` that is used to generate 74 | the test data for the unit tests. When adding or modifying tests or data, the 75 | test data must be kept in sync with the expected results in the unit tests. 76 | -------------------------------------------------------------------------------- /igvm_c/cbindgen_igvm.toml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT OR Apache-2.0 2 | # 3 | # Copyright (c) 2023 SUSE LLC 4 | # 5 | # Author: Roy Hopkins 6 | 7 | language = "C" 8 | 9 | header = """ 10 | /* 11 | * SPDX-License-Identifier: MIT OR Apache-2.0 12 | * 13 | * Copyright (c) 2023 SUSE LLC 14 | * 15 | * Author: Roy Hopkins 16 | */ 17 | """ 18 | 19 | include_guard = "IGVM_H" 20 | 21 | includes = ["igvm_defs.h"] 22 | 23 | autogen_warning = "/* This file is generated using cbindgen. Do not edit this file directly. */" 24 | 25 | include_version = true 26 | 27 | no_includes = true 28 | 29 | [export] 30 | include = ["IgvmResult"] 31 | 32 | [export.rename] 33 | "X64_PAGE_SIZE" = "IGVM_X64_PAGE_SIZE" 34 | "X64_LARGE_PAGE_SIZE" = "IGVM_X64_LARGE_PAGE_SIZE" 35 | "X64_1GB_PAGE_SIZE" = "IGVM_X64_1GB_PAGE_SIZE" -------------------------------------------------------------------------------- /igvm_c/cbindgen_igvm_defs.toml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT OR Apache-2.0 2 | # 3 | # Copyright (c) 2023 SUSE LLC 4 | # 5 | # Author: Roy Hopkins 6 | 7 | language = "C" 8 | 9 | header = """ 10 | /* 11 | * SPDX-License-Identifier: MIT OR Apache-2.0 12 | * 13 | * Copyright (c) 2023 SUSE LLC 14 | * 15 | * Author: Roy Hopkins 16 | */ 17 | """ 18 | 19 | include_guard = "IGVM_DEFS_H" 20 | 21 | autogen_warning = "/* This file is generated using cbindgen. Do not edit this file directly. */" 22 | 23 | include_version = true 24 | 25 | after_includes = """ 26 | 27 | typedef struct { 28 | uint32_t is_2mb_page : 1; 29 | uint32_t unmeasured : 1; 30 | uint32_t reserved : 30; 31 | } IgvmPageDataFlags; 32 | 33 | typedef struct { 34 | uint32_t vtl2_protectable : 1; 35 | uint32_t reserved : 31; 36 | } RequiredMemoryFlags; 37 | 38 | typedef struct { 39 | uint16_t is_shared : 1; 40 | uint16_t reserved : 15; 41 | } MemoryMapEntryFlags; 42 | 43 | typedef struct { 44 | uint32_t memory_is_shared : 1; 45 | uint32_t reserved : 31; 46 | } IgvmEnvironmentInfo; 47 | 48 | typedef uint64_t IGVM_U64_LE; 49 | typedef uint32_t IGVM_U32_LE; 50 | 51 | #define IGVM_UINT32_FLAGS_VALUE(x) *((uint32_t*)&(x)) 52 | """ 53 | 54 | [export] 55 | include = ["IGVM_FIXED_HEADER", 56 | "IGVM_VHS_VARIABLE_HEADER", 57 | "IGVM_VHS_SUPPORTED_PLATFORM", 58 | "IGVM_VHS_GUEST_POLICY", 59 | "GuestPolicy", 60 | "SnpPolicy", 61 | "TdxPolicy", 62 | "IGVM_VHS_RELOCATABLE_REGION", 63 | "IGVM_VHS_PAGE_TABLE_RELOCATION", 64 | "IGVM_VHS_PARAMETER_AREA", 65 | "IGVM_VHS_PAGE_DATA", 66 | "IGVM_VHS_PARAMETER_INSERT", 67 | "IGVM_VHS_PARAMETER", 68 | "IGVM_VHS_VP_CONTEXT", 69 | "VbsVpContextHeader", 70 | "VbsVpContextRegister", 71 | "IGVM_VHS_REQUIRED_MEMORY", 72 | "IGVM_VHS_MEMORY_RANGE", 73 | "IGVM_VHS_MMIO_RANGES", 74 | "IGVM_VHS_SNP_ID_BLOCK_SIGNATURE", 75 | "IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY", 76 | "IGVM_VHS_SNP_ID_BLOCK", 77 | "IGVM_VHS_VBS_MEASUREMENT", 78 | "IGVM_VHS_MEMORY_MAP_ENTRY", 79 | "IGVM_VHS_ERROR_RANGE"] 80 | 81 | exclude = ["IgvmPageDataFlags", "RequiredMemoryFlags", "MemoryMapEntryFlags"] 82 | 83 | [export.rename] 84 | "MemoryMapEntryType" = "IgvmMemoryMapEntryType" 85 | "SnpPolicy" = "IgvmSnpPolicy" 86 | "TdxPolicy" = "IgvmTdxPolicy" 87 | "VbsVpContextHeader" = "IgvmVbsVpContextHeader" 88 | "VbsVpContextRegister" = "IgvmVbsVpContextRegister" 89 | "PAGE_SIZE_4K" = "IGVM_PAGE_SIZE_4K" 90 | "u64_le" = "IGVM_U64_LE" 91 | "u32_le" = "IGVM_U32_LE" -------------------------------------------------------------------------------- /igvm_c/igvm.pc.in: -------------------------------------------------------------------------------- 1 | prefix=$PREFIX 2 | exec_prefix=${prefix} 3 | libdir=${prefix}/lib64 4 | sharedlibdir=${libdir} 5 | includedir=${prefix}/include 6 | 7 | Name: igvm 8 | Description: igvm library 9 | Version: $VERSION 10 | 11 | Requires: 12 | Libs: -L${libdir} -L${sharedlibdir} -ligvm 13 | Cflags: -I${includedir} 14 | -------------------------------------------------------------------------------- /igvm_c/sample/dump_igvm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: MIT OR Apache-2.0 3 | * 4 | * Copyright (c) 2023 SUSE LLC 5 | * 6 | * Author: Roy Hopkins 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include "../include/igvm.h" 12 | 13 | static char* filename = NULL; 14 | static int hex_context = INT_MAX; 15 | static char *section_name[] = { "platform", "initialization", "directive" }; 16 | 17 | static void hexdump(const void* data, size_t size, int columns, int address) { 18 | int rows = (size + (columns - 1)) / columns; 19 | int row; 20 | int col; 21 | 22 | for (row = 0; row < rows; ++row) { 23 | printf("| %08X | ", address + row * columns); 24 | for (col = 0; col < columns; ++col) { 25 | size_t index = row * columns + col; 26 | if (index >= size) { 27 | printf(" "); 28 | } 29 | else { 30 | printf("%02X ", ((unsigned char *)data)[index]); 31 | } 32 | } 33 | printf("| "); 34 | for (col = 0; col < columns; ++col) { 35 | size_t index = row * columns + col; 36 | if (index >= size) { 37 | printf(" "); 38 | } 39 | else { 40 | char c = ((char *)data)[index]; 41 | if ((c >= 32) && (c < 127)) { 42 | printf("%c", c); 43 | } 44 | else { 45 | printf("."); 46 | } 47 | } 48 | } 49 | printf(" |\n"); 50 | } 51 | } 52 | 53 | static char *igvm_type_to_text(uint32_t type) 54 | { 55 | switch (type & 0x7fffffff) { 56 | case IGVM_VHT_SUPPORTED_PLATFORM: 57 | return "IGVM_VHT_SUPPORTED_PLATFORM"; 58 | case IGVM_VHT_GUEST_POLICY: 59 | return "IGVM_VHT_GUEST_POLICY"; 60 | case IGVM_VHT_RELOCATABLE_REGION: 61 | return "IGVM_VHT_RELOCATABLE_REGION"; 62 | case IGVM_VHT_PAGE_TABLE_RELOCATION_REGION: 63 | return "IGVM_VHT_PAGE_TABLE_RELOCATION_REGION"; 64 | case IGVM_VHT_PARAMETER_AREA: 65 | return "IGVM_VHT_PARAMETER_AREA"; 66 | case IGVM_VHT_PAGE_DATA: 67 | return "IGVM_VHT_PAGE_DATA"; 68 | case IGVM_VHT_PARAMETER_INSERT: 69 | return "IGVM_VHT_PARAMETER_INSERT"; 70 | case IGVM_VHT_VP_CONTEXT: 71 | return "IGVM_VHT_VP_CONTEXT"; 72 | case IGVM_VHT_REQUIRED_MEMORY: 73 | return "IGVM_VHT_REQUIRED_MEMORY"; 74 | case IGVM_VHT_VP_COUNT_PARAMETER: 75 | return "IGVM_VHT_VP_COUNT_PARAMETER"; 76 | case IGVM_VHT_SRAT: 77 | return "IGVM_VHT_SRAT"; 78 | case IGVM_VHT_MADT: 79 | return "IGVM_VHT_MADT"; 80 | case IGVM_VHT_MMIO_RANGES: 81 | return "IGVM_VHT_MMIO_RANGES"; 82 | case IGVM_VHT_SNP_ID_BLOCK: 83 | return "IGVM_VHT_SNP_ID_BLOCK"; 84 | case IGVM_VHT_MEMORY_MAP: 85 | return "IGVM_VHT_MEMORY_MAP"; 86 | case IGVM_VHT_ERROR_RANGE: 87 | return "IGVM_VHT_ERROR_RANGE"; 88 | case IGVM_VHT_COMMAND_LINE: 89 | return "IGVM_VHT_COMMAND_LINE"; 90 | case IGVM_VHT_SLIT: 91 | return "IGVM_VHT_SLIT"; 92 | case IGVM_VHT_PPTT: 93 | return "IGVM_VHT_PPTT"; 94 | case IGVM_VHT_VBS_MEASUREMENT: 95 | return "IGVM_VHT_VBS_MEASUREMENT"; 96 | case IGVM_VHT_DEVICE_TREE: 97 | return "IGVM_VHT_DEVICE_TREE"; 98 | case IGVM_VHT_ENVIRONMENT_INFO_PARAMETER: 99 | return "IGVM_VHT_ENVIRONMENT_INFO_PARAMETER"; 100 | default: 101 | return "Unknown type"; 102 | } 103 | } 104 | 105 | static void igvm_dump_parameter(IGVM_VHS_PARAMETER *param) 106 | { 107 | printf(" IGVM_VHS_PARAMETER:\n"); 108 | printf(" ParameterPageIndex: %08X\n", param->parameter_area_index); 109 | printf(" ByteOffset: %08X\n", param->byte_offset); 110 | printf("\n"); 111 | } 112 | 113 | static void igvm_dump_variable_header(IGVM_VHS_VARIABLE_HEADER *header) 114 | { 115 | void* vh_data = (uint8_t *)header + sizeof(IGVM_VHS_VARIABLE_HEADER); 116 | printf("%s:\n", igvm_type_to_text(header->typ)); 117 | switch (header->typ) { 118 | case IGVM_VHT_SUPPORTED_PLATFORM: { 119 | IGVM_VHS_SUPPORTED_PLATFORM *vhs = 120 | (IGVM_VHS_SUPPORTED_PLATFORM *)vh_data; 121 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 122 | printf(" HighestVtl: %02X\n", vhs->highest_vtl); 123 | printf(" PlatformType: %02X\n", vhs->platform_type); 124 | printf(" PlatformVersion: %04X\n", vhs->platform_version); 125 | printf(" SharedGPABoundary: %lX\n", vhs->shared_gpa_boundary); 126 | break; 127 | } 128 | case IGVM_VHT_GUEST_POLICY: { 129 | IGVM_VHS_GUEST_POLICY *vhs = (IGVM_VHS_GUEST_POLICY *)vh_data; 130 | printf(" Policy: %016lX\n", vhs->policy); 131 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 132 | printf(" Reserved: %08X\n", vhs->reserved); 133 | break; 134 | } 135 | case IGVM_VHT_RELOCATABLE_REGION: { 136 | IGVM_VHS_RELOCATABLE_REGION *vhs = 137 | (IGVM_VHS_RELOCATABLE_REGION *)vh_data; 138 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 139 | printf(" VpIndex: %04X\n", vhs->vp_index); 140 | printf(" VTL: %02X\n", vhs->vtl); 141 | printf(" Flags: %02X\n", vhs->flags); 142 | printf(" RelocationAlignment: %016lX\n", 143 | vhs->relocation_alignment); 144 | printf(" RelocationRegionGPA: %016lX\n", 145 | vhs->relocation_region_gpa); 146 | printf(" RelocationRegionSize: %016lX\n", 147 | vhs->relocation_region_size); 148 | printf(" MinimumRelocationGPA: %016lX\n", 149 | vhs->minimum_relocation_gpa); 150 | printf(" MaximumRelocationGPA: %016lX\n", 151 | vhs->maximum_relocation_gpa); 152 | break; 153 | } 154 | case IGVM_VHT_PAGE_TABLE_RELOCATION_REGION: { 155 | IGVM_VHS_PAGE_TABLE_RELOCATION *vhs = 156 | (IGVM_VHS_PAGE_TABLE_RELOCATION *)vh_data; 157 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 158 | printf(" VpIndex: %04X\n", vhs->vp_index); 159 | printf(" VTL: %02X\n", vhs->vtl); 160 | printf(" Reserved: %02X\n", vhs->reserved); 161 | printf(" GPA: %016lX\n", vhs->gpa); 162 | printf(" Size: %016lX\n", vhs->size); 163 | printf(" UsedSize: %016lX\n", vhs->used_size); 164 | break; 165 | } 166 | case IGVM_VHT_PARAMETER_AREA: { 167 | IGVM_VHS_PARAMETER_AREA *vhs = 168 | (IGVM_VHS_PARAMETER_AREA *)vh_data; 169 | printf(" NumberOfBytes: %016lX\n", vhs->number_of_bytes); 170 | printf(" ParameterAreaIndex: %08X\n", 171 | vhs->parameter_area_index); 172 | printf(" FileOffset: %08X\n", vhs->file_offset); 173 | break; 174 | } 175 | case IGVM_VHT_PAGE_DATA: { 176 | IGVM_VHS_PAGE_DATA *vhs = (IGVM_VHS_PAGE_DATA *)vh_data; 177 | printf(" GPA: %016lX\n", vhs->gpa); 178 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 179 | printf(" FileOffset: %08X\n", vhs->file_offset); 180 | printf(" Flags: %08X\n", IGVM_UINT32_FLAGS_VALUE(vhs->flags)); 181 | printf(" Reserved: %08X\n", vhs->reserved); 182 | break; 183 | } 184 | case IGVM_VHT_PARAMETER_INSERT: { 185 | IGVM_VHS_PARAMETER_INSERT *vhs = 186 | (IGVM_VHS_PARAMETER_INSERT *)vh_data; 187 | printf(" GPA: %016lX\n", vhs->gpa); 188 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 189 | printf(" ParameterAreaIndex: %08X\n", 190 | vhs->parameter_area_index); 191 | break; 192 | } 193 | case IGVM_VHT_VP_CONTEXT: { 194 | IGVM_VHS_VP_CONTEXT *vhs = (IGVM_VHS_VP_CONTEXT *)vh_data; 195 | printf(" GPA: %016lX\n", vhs->gpa); 196 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 197 | printf(" FileOffset: %08X\n", vhs->file_offset); 198 | printf(" VPIndex: %04X\n", vhs->vp_index); 199 | printf(" Reserved: %04X\n", vhs->reserved); 200 | break; 201 | } 202 | case IGVM_VHT_REQUIRED_MEMORY: { 203 | IGVM_VHS_REQUIRED_MEMORY *vhs = 204 | (IGVM_VHS_REQUIRED_MEMORY *)vh_data; 205 | printf(" GPA: %016lX\n", vhs->gpa); 206 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 207 | printf(" NumberOfBytes: %08X\n", vhs->number_of_bytes); 208 | printf(" Flags: %08X\n", IGVM_UINT32_FLAGS_VALUE(vhs->flags)); 209 | printf(" Reserved: %08X\n", vhs->reserved); 210 | break; 211 | } 212 | case IGVM_VHT_VP_COUNT_PARAMETER: 213 | case IGVM_VHT_SRAT: 214 | case IGVM_VHT_MADT: 215 | case IGVM_VHT_MMIO_RANGES: 216 | case IGVM_VHT_MEMORY_MAP: 217 | case IGVM_VHT_COMMAND_LINE: 218 | case IGVM_VHT_ENVIRONMENT_INFO_PARAMETER: 219 | case IGVM_VHT_SLIT: 220 | case IGVM_VHT_PPTT: { 221 | IGVM_VHS_PARAMETER *vhs = (IGVM_VHS_PARAMETER *)vh_data; 222 | igvm_dump_parameter(vhs); 223 | break; 224 | } 225 | case IGVM_VHT_SNP_ID_BLOCK: { 226 | IGVM_VHS_SNP_ID_BLOCK *vhs = (IGVM_VHS_SNP_ID_BLOCK *)vh_data; 227 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 228 | printf(" AuthorKeyEnabled: %02X\n", vhs->author_key_enabled); 229 | printf(" Reserved: %02X%02X%02X\n", vhs->reserved[0], 230 | vhs->reserved[1], vhs->reserved[2]); 231 | printf(" Ld:\n"); 232 | hexdump(vhs->ld, 32, 16, 0); 233 | printf(" FamilyId:\n"); 234 | hexdump(vhs->ld, 16, 16, 0); 235 | printf(" ImageId:\n"); 236 | hexdump(vhs->ld, 16, 16, 0); 237 | printf(" Version: %08X\n", vhs->version); 238 | printf(" GuestSvn: %08X\n", vhs->guest_svn); 239 | printf(" IdKeyAlgorithm: %08X\n", vhs->id_key_algorithm); 240 | printf(" AuthorKeyAlgorithm: %08X\n", 241 | vhs->author_key_algorithm); 242 | break; 243 | } 244 | case IGVM_VHT_ERROR_RANGE: { 245 | IGVM_VHS_ERROR_RANGE *vhs = 246 | (IGVM_VHS_ERROR_RANGE *)vh_data; 247 | printf(" GPA: %016lX\n", vhs->gpa); 248 | printf(" CompatibilityMask: %08X\n", vhs->compatibility_mask); 249 | printf(" SizeBytes: %08X\n", vhs->size_bytes); 250 | break; 251 | } 252 | default: 253 | break; 254 | } 255 | printf("\n"); 256 | } 257 | 258 | static void igvm_dump_fixed_header(IGVM_FIXED_HEADER *header) 259 | { 260 | printf("IGVM_FIXED_HEADER:\n"); 261 | printf(" Magic: 0x%08X\n", header->magic); 262 | printf(" FormatVersion: 0x%08X\n", header->format_version); 263 | printf(" VariableHeaderOffset: 0x%08X\n", 264 | header->variable_header_offset); 265 | printf(" VariableHeaderSize: 0x%08X\n", header->variable_header_size); 266 | printf(" TotalFileSize: 0x%08X\n", header->total_file_size); 267 | printf(" Checksum: 0x%08X\n", header->checksum); 268 | printf("\n"); 269 | } 270 | 271 | static int dump_igvm(uint8_t* igvm_buf, unsigned long igvm_length) 272 | { 273 | IgvmHandle igvm; 274 | if ((igvm = igvm_new_from_binary(igvm_buf, igvm_length)) < 0) { 275 | printf("Failed to parse IGVM file. Error code: %ld\n", igvm); 276 | return 1; 277 | } 278 | 279 | for (long section = 0; section <= IGVM_HEADER_SECTION_DIRECTIVE; ++section) { 280 | int32_t count = igvm_header_count(igvm, (IgvmHeaderSection)section); 281 | printf("----------------------------------------------------------\n" 282 | "%s count = %ld\n\n", 283 | section_name[section], count); 284 | 285 | for (long i = 0; i < count; ++i) { 286 | IgvmVariableHeaderType typ = igvm_get_header_type(igvm, section, i); 287 | if (typ > 0) { 288 | IgvmHandle header_handle; 289 | IgvmHandle header_data; 290 | 291 | header_handle = igvm_get_header(igvm, section, i); 292 | if (header_handle < 0) { 293 | printf("Invalid header (%ld)\n", header_handle); 294 | return 1; 295 | } 296 | igvm_dump_variable_header((IGVM_VHS_VARIABLE_HEADER*)igvm_get_buffer(igvm, header_handle)); 297 | igvm_free_buffer(igvm, header_handle); 298 | 299 | /* Do the same for any associated file data */ 300 | header_data = igvm_get_header_data(igvm, section, i); 301 | if (header_data > 0) { 302 | uint32_t filedata_length = igvm_get_buffer_size(igvm, header_data); 303 | printf("Got %u bytes of file data:\n", filedata_length); 304 | hexdump(igvm_get_buffer(igvm, header_data), (filedata_length > hex_context) ? hex_context : filedata_length, 32, 0); 305 | igvm_free_buffer(igvm, header_data); 306 | } 307 | } 308 | } 309 | } 310 | 311 | igvm_free(igvm); 312 | return 0; 313 | } 314 | 315 | static int parse_args(int argc, char **argv) { 316 | int i; 317 | for (i = 1; i < argc; ++i) { 318 | if (argv[i][0] != '-') { 319 | if (filename != NULL) { 320 | printf("Invalid command line\n"); 321 | return 1; 322 | } 323 | filename = argv[i]; 324 | } 325 | else if (strcmp(argv[i], "--hex") == 0 || strcmp(argv[i], "-h") == 0) { 326 | if ((i + 1) == argc) { 327 | printf("Value missing for --hex\n"); 328 | return 1; 329 | } 330 | ++i; 331 | hex_context = atoi(argv[i]); 332 | } 333 | else { 334 | printf("Invalid argument: %s\n", argv[i]); 335 | return 1; 336 | } 337 | } 338 | if (!filename) { 339 | printf("Filename not provided\n"); 340 | return 1; 341 | } 342 | return 0; 343 | } 344 | 345 | int main(int argc, char **argv) { 346 | FILE *fp; 347 | unsigned long length; 348 | uint8_t *igvm_buf = NULL; 349 | int ret; 350 | 351 | if (parse_args(argc, argv)) { 352 | printf("Usage: dump_igvm [--hex|-h bytes] igvm_file\n"); 353 | printf(" --hex bytes specifies how many bytes of " 354 | "each file data section to dump as hex. Defaults " 355 | "to dumping the entire section.\n"); 356 | return 1; 357 | } 358 | 359 | fp = fopen(filename, "rb"); 360 | if (!fp) { 361 | printf("Could not open file\n"); 362 | return 1; 363 | } 364 | fseek(fp, 0, SEEK_END); 365 | length = ftell(fp); 366 | fseek(fp, 0, SEEK_SET); 367 | 368 | igvm_buf = (uint8_t *)malloc(length); 369 | if (!igvm_buf) { 370 | fclose(fp); 371 | printf("Could not allocate buffer to read file\n"); 372 | return 1; 373 | } 374 | if (fread(igvm_buf, 1, length, fp) != length) { 375 | fclose(fp); 376 | free(igvm_buf); 377 | printf("Failed to read file\n"); 378 | return 1; 379 | } 380 | fclose(fp); 381 | 382 | ret = dump_igvm(igvm_buf, length); 383 | 384 | free(igvm_buf); 385 | return ret; 386 | } -------------------------------------------------------------------------------- /igvm_c/scripts/post_process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # Copyright (c) 2024 SUSE LLC 5 | # 6 | # Author: Roy Hopkins 7 | # 8 | # A script to post-process the header files generated with cbindgen 9 | # to rename items which could not be directly handled by the 10 | # cbindgen configuration files and annotations. 11 | set -e 12 | 13 | sed -i -e 's/INVALID = 0/IGVM_INVALID = 0/g' \ 14 | -e 's/RESERVED_DO_NOT_USE = /IGVM_RESERVED_DO_NOT_USE = /g' \ 15 | -e 's/RequiredMemoryFlags/IgvmRequiredMemoryFlags/g' \ 16 | -e 's/MemoryMapEntryFlags/IgvmMemoryMapEntryFlags/g' \ 17 | $1/igvm_defs.h 18 | 19 | sed -i -e 's/ HEADER_SECTION_/ IGVM_HEADER_SECTION_/g' \ 20 | $1/igvm.h 21 | -------------------------------------------------------------------------------- /igvm_c/test_data/Cargo.toml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT OR Apache-2.0 2 | # 3 | # Copyright (c) 2023 SUSE LLC 4 | # 5 | # Author: Roy Hopkins 6 | 7 | [package] 8 | name = "test_data" 9 | version = "0.1.0" 10 | edition = "2021" 11 | description = "Generate test data for the C API tests" 12 | authors = ["SUSE"] 13 | 14 | [dependencies] 15 | igvm = { workspace = true } 16 | igvm_defs = { workspace = true, features = ["unstable"] } 17 | 18 | -------------------------------------------------------------------------------- /igvm_c/test_data/src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | // 3 | // Copyright (c) 2023 SUSE LLC 4 | // 5 | // Author: Roy Hopkins 6 | 7 | //! Implementation of a tool that generates a simple IGVM file that is used 8 | //! to generate test data for the C API unit tests. 9 | 10 | use std::env; 11 | use std::fs::File; 12 | use std::io::Write; 13 | 14 | use igvm::IgvmDirectiveHeader; 15 | use igvm::IgvmFile; 16 | use igvm::IgvmInitializationHeader; 17 | use igvm::IgvmPlatformHeader; 18 | use igvm::IgvmRevision; 19 | use igvm_defs::IgvmPageDataFlags; 20 | use igvm_defs::IgvmPageDataType; 21 | use igvm_defs::IgvmPlatformType; 22 | use igvm_defs::IGVM_VHS_PARAMETER; 23 | use igvm_defs::IGVM_VHS_PARAMETER_INSERT; 24 | use igvm_defs::IGVM_VHS_SUPPORTED_PLATFORM; 25 | use igvm_defs::PAGE_SIZE_4K; 26 | 27 | fn new_platform(compatibility_mask: u32, platform_type: IgvmPlatformType) -> IgvmPlatformHeader { 28 | IgvmPlatformHeader::SupportedPlatform(IGVM_VHS_SUPPORTED_PLATFORM { 29 | compatibility_mask, 30 | highest_vtl: 0, 31 | platform_type, 32 | platform_version: 1, 33 | shared_gpa_boundary: 0, 34 | }) 35 | } 36 | 37 | fn new_guest_policy(policy: u64, compatibility_mask: u32) -> IgvmInitializationHeader { 38 | IgvmInitializationHeader::GuestPolicy { 39 | policy, 40 | compatibility_mask, 41 | } 42 | } 43 | 44 | fn new_page_data(page: u64, compatibility_mask: u32, data: &[u8]) -> IgvmDirectiveHeader { 45 | IgvmDirectiveHeader::PageData { 46 | gpa: page * PAGE_SIZE_4K, 47 | compatibility_mask, 48 | flags: IgvmPageDataFlags::new(), 49 | data_type: IgvmPageDataType::NORMAL, 50 | data: data.to_vec(), 51 | } 52 | } 53 | 54 | fn new_parameter_area(index: u32) -> IgvmDirectiveHeader { 55 | IgvmDirectiveHeader::ParameterArea { 56 | number_of_bytes: 4096, 57 | parameter_area_index: index, 58 | initial_data: vec![], 59 | } 60 | } 61 | 62 | fn new_parameter_usage(index: u32) -> IgvmDirectiveHeader { 63 | IgvmDirectiveHeader::VpCount(IGVM_VHS_PARAMETER { 64 | parameter_area_index: index, 65 | byte_offset: 0, 66 | }) 67 | } 68 | 69 | fn new_parameter_insert(page: u64, index: u32, mask: u32) -> IgvmDirectiveHeader { 70 | IgvmDirectiveHeader::ParameterInsert(IGVM_VHS_PARAMETER_INSERT { 71 | gpa: page * PAGE_SIZE_4K, 72 | parameter_area_index: index, 73 | compatibility_mask: mask, 74 | }) 75 | } 76 | 77 | fn create_basic(filename: &String) { 78 | let data1 = vec![1; PAGE_SIZE_4K as usize]; 79 | let data2 = vec![2; PAGE_SIZE_4K as usize]; 80 | let data3 = vec![3; PAGE_SIZE_4K as usize]; 81 | let data4 = vec![4; PAGE_SIZE_4K as usize]; 82 | let file = IgvmFile::new( 83 | IgvmRevision::V1, 84 | vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)], 85 | vec![new_guest_policy(0x30000, 1), new_guest_policy(0x30000, 2)], 86 | vec![ 87 | new_page_data(0, 1, &data1), 88 | new_page_data(1, 1, &data2), 89 | new_page_data(2, 1, &data3), 90 | new_page_data(4, 1, &data4), 91 | new_page_data(10, 1, &data1), 92 | new_page_data(11, 1, &data2), 93 | new_page_data(12, 1, &data3), 94 | new_page_data(14, 1, &data4), 95 | new_parameter_area(0), 96 | new_parameter_usage(0), 97 | new_parameter_insert(20, 0, 1), 98 | ], 99 | ) 100 | .expect("Failed to create file"); 101 | let mut binary_file = Vec::new(); 102 | file.serialize(&mut binary_file).unwrap(); 103 | 104 | let mut file = File::create(filename).expect("Could not open file"); 105 | file.write_all(binary_file.as_slice()) 106 | .expect("Failed to write file"); 107 | } 108 | 109 | fn main() { 110 | let args: Vec = env::args().collect(); 111 | if args.len() != 2 { 112 | println!("Usage: test_data igvm_filename"); 113 | return; 114 | } 115 | create_basic(&args[1]); 116 | } 117 | -------------------------------------------------------------------------------- /igvm_c/tests/igvm_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../include/igvm.h" 5 | #include 6 | 7 | static const char *filename = NULL; 8 | static uint8_t* igvm_buf; 9 | static uint32_t igvm_buf_length; 10 | static uint8_t invalid_igvm_buf[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; 11 | 12 | /* These values must match the test data prodcued by the test_data executable */ 13 | #define TEST_DATA_NUM_PLATFORM 1 14 | #define TEST_DATA_NUM_INITIALIZATION 2 15 | #define TEST_DATA_NUM_DIRECTIVE 11 16 | 17 | 18 | void test_valid_igvm(void) { 19 | IgvmHandle igvm; 20 | igvm = igvm_new_from_binary(igvm_buf, igvm_buf_length); 21 | CU_ASSERT(igvm > 0); 22 | igvm_free(igvm); 23 | } 24 | 25 | void test_invalid_fixed_header(void) { 26 | IgvmHandle igvm; 27 | CU_ASSERT_EQUAL(igvm_new_from_binary(invalid_igvm_buf, sizeof(invalid_igvm_buf)), IGVMAPI_INVALID_FIXED_HEADER); 28 | } 29 | 30 | void test_header_counts(void) { 31 | IgvmHandle igvm; 32 | igvm = igvm_new_from_binary(igvm_buf, igvm_buf_length); 33 | CU_ASSERT(igvm > 0); 34 | 35 | CU_ASSERT_EQUAL(igvm_header_count(igvm, IGVM_HEADER_SECTION_PLATFORM), 1); 36 | CU_ASSERT_EQUAL(igvm_header_count(igvm, IGVM_HEADER_SECTION_INITIALIZATION), 2); 37 | CU_ASSERT_EQUAL(igvm_header_count(igvm, IGVM_HEADER_SECTION_DIRECTIVE), 11); 38 | 39 | igvm_free(igvm); 40 | } 41 | 42 | void test_platform_header(void) { 43 | IgvmHandle igvm; 44 | IGVM_VHS_VARIABLE_HEADER *header; 45 | IGVM_VHS_SUPPORTED_PLATFORM *platform; 46 | IgvmHandle data; 47 | 48 | igvm = igvm_new_from_binary(igvm_buf, igvm_buf_length); 49 | CU_ASSERT(igvm > 0); 50 | data = igvm_get_header(igvm, IGVM_HEADER_SECTION_PLATFORM, 0); 51 | CU_ASSERT(data > 0); 52 | 53 | header = (IGVM_VHS_VARIABLE_HEADER *)igvm_get_buffer(igvm, data); 54 | CU_ASSERT_EQUAL(header->typ, IGVM_VHT_SUPPORTED_PLATFORM); 55 | CU_ASSERT_EQUAL(header->length, sizeof(IGVM_VHS_SUPPORTED_PLATFORM)); 56 | 57 | platform = (IGVM_VHS_SUPPORTED_PLATFORM *)(igvm_get_buffer(igvm, data) + sizeof(IGVM_VHS_VARIABLE_HEADER)); 58 | CU_ASSERT_EQUAL(platform->platform_type, IGVM_PLATFORM_TYPE_VSM_ISOLATION); 59 | CU_ASSERT_EQUAL(platform->compatibility_mask, 1); 60 | CU_ASSERT_EQUAL(platform->platform_version, 1); 61 | CU_ASSERT_EQUAL(platform->highest_vtl, 0); 62 | CU_ASSERT_EQUAL(platform->shared_gpa_boundary, 0); 63 | 64 | igvm_free_buffer(igvm, data); 65 | igvm_free(igvm); 66 | } 67 | 68 | void test_initialization_header(void) { 69 | IgvmHandle igvm; 70 | IGVM_VHS_VARIABLE_HEADER *header; 71 | IGVM_VHS_GUEST_POLICY *policy; 72 | IgvmHandle data; 73 | 74 | igvm = igvm_new_from_binary(igvm_buf, igvm_buf_length); 75 | CU_ASSERT(igvm > 0); 76 | data = igvm_get_header(igvm, IGVM_HEADER_SECTION_INITIALIZATION, 0); 77 | CU_ASSERT(data > 0); 78 | 79 | /* First entry */ 80 | header = (IGVM_VHS_VARIABLE_HEADER *)igvm_get_buffer(igvm, data); 81 | CU_ASSERT_EQUAL(header->typ, IGVM_VHT_GUEST_POLICY); 82 | CU_ASSERT_EQUAL(header->length, sizeof(IGVM_VHS_GUEST_POLICY)); 83 | 84 | policy = (IGVM_VHS_GUEST_POLICY *)(igvm_get_buffer(igvm, data) + sizeof(IGVM_VHS_VARIABLE_HEADER)); 85 | CU_ASSERT_EQUAL(policy->policy, 0x30000); 86 | CU_ASSERT_EQUAL(policy->compatibility_mask, 1); 87 | CU_ASSERT_EQUAL(policy->reserved, 0); 88 | igvm_free_buffer(igvm, data); 89 | 90 | /* Second entry */ 91 | data = igvm_get_header(igvm, IGVM_HEADER_SECTION_INITIALIZATION, 1); 92 | CU_ASSERT(data > 0); 93 | 94 | header = (IGVM_VHS_VARIABLE_HEADER *)igvm_get_buffer(igvm, data); 95 | CU_ASSERT_EQUAL(header->typ, IGVM_VHT_GUEST_POLICY); 96 | CU_ASSERT_EQUAL(header->length, sizeof(IGVM_VHS_GUEST_POLICY)); 97 | 98 | policy = (IGVM_VHS_GUEST_POLICY *)(igvm_get_buffer(igvm, data) + sizeof(IGVM_VHS_VARIABLE_HEADER)); 99 | CU_ASSERT_EQUAL(policy->policy, 0x30000); 100 | CU_ASSERT_EQUAL(policy->compatibility_mask, 2); 101 | CU_ASSERT_EQUAL(policy->reserved, 0); 102 | igvm_free_buffer(igvm, data); 103 | 104 | igvm_free(igvm); 105 | } 106 | 107 | void test_directive_header(void) { 108 | IgvmHandle igvm; 109 | IGVM_VHS_VARIABLE_HEADER *header; 110 | IGVM_VHS_PAGE_DATA *page; 111 | IGVM_VHS_PARAMETER_AREA *param_area; 112 | IGVM_VHS_PARAMETER* param; 113 | IGVM_VHS_PARAMETER_INSERT* ins; 114 | IgvmHandle data; 115 | 116 | igvm = igvm_new_from_binary(igvm_buf, igvm_buf_length); 117 | CU_ASSERT(igvm > 0); 118 | data = igvm_get_header(igvm, IGVM_HEADER_SECTION_DIRECTIVE, 1); 119 | CU_ASSERT(data > 0); 120 | 121 | header = (IGVM_VHS_VARIABLE_HEADER *)igvm_get_buffer(igvm, data); 122 | CU_ASSERT_EQUAL(header->typ, IGVM_VHT_PAGE_DATA); 123 | CU_ASSERT_EQUAL(header->length, sizeof(IGVM_VHS_PAGE_DATA)); 124 | page = (IGVM_VHS_PAGE_DATA *)(igvm_get_buffer(igvm, data) + sizeof(IGVM_VHS_VARIABLE_HEADER)); 125 | CU_ASSERT_EQUAL(page->data_type, IGVM_PAGE_DATA_TYPE_NORMAL); 126 | CU_ASSERT_EQUAL(page->compatibility_mask, 1); 127 | CU_ASSERT_EQUAL(page->file_offset, 0); 128 | CU_ASSERT_EQUAL(page->flags.is_2mb_page, 0); 129 | CU_ASSERT_EQUAL(page->flags.unmeasured, 0); 130 | CU_ASSERT_EQUAL(page->flags.reserved, 0); 131 | CU_ASSERT_EQUAL(page->gpa, 0x1000); 132 | CU_ASSERT_EQUAL(page->reserved, 0); 133 | igvm_free_buffer(igvm, data); 134 | 135 | data = igvm_get_header(igvm, IGVM_HEADER_SECTION_DIRECTIVE, 8); 136 | header = (IGVM_VHS_VARIABLE_HEADER *)igvm_get_buffer(igvm, data); 137 | CU_ASSERT_EQUAL(header->typ, IGVM_VHT_PARAMETER_AREA); 138 | CU_ASSERT_EQUAL(header->length, sizeof(IGVM_VHS_PARAMETER_AREA)); 139 | param_area = (IGVM_VHS_PARAMETER_AREA *)(igvm_get_buffer(igvm, data) + sizeof(IGVM_VHS_VARIABLE_HEADER)); 140 | CU_ASSERT_EQUAL(param_area->parameter_area_index, 0); 141 | CU_ASSERT_EQUAL(param_area->file_offset, 0); 142 | CU_ASSERT_EQUAL(param_area->number_of_bytes, 0x1000); 143 | igvm_free_buffer(igvm, data); 144 | 145 | data = igvm_get_header(igvm, IGVM_HEADER_SECTION_DIRECTIVE, 9); 146 | CU_ASSERT(data > 0); 147 | header = (IGVM_VHS_VARIABLE_HEADER *)igvm_get_buffer(igvm, data); 148 | CU_ASSERT_EQUAL(header->typ, IGVM_VHT_VP_COUNT_PARAMETER); 149 | CU_ASSERT_EQUAL(header->length, sizeof(IGVM_VHS_PARAMETER)); 150 | param = (IGVM_VHS_PARAMETER *)(igvm_get_buffer(igvm, data) + sizeof(IGVM_VHS_VARIABLE_HEADER)); 151 | CU_ASSERT_EQUAL(param->parameter_area_index, 0); 152 | CU_ASSERT_EQUAL(param->byte_offset, 0); 153 | igvm_free_buffer(igvm, data); 154 | 155 | data = igvm_get_header(igvm, IGVM_HEADER_SECTION_DIRECTIVE, 10); 156 | CU_ASSERT(data > 0); 157 | header = (IGVM_VHS_VARIABLE_HEADER *)igvm_get_buffer(igvm, data); 158 | CU_ASSERT_EQUAL(header->typ, IGVM_VHT_PARAMETER_INSERT); 159 | CU_ASSERT_EQUAL(header->length, sizeof(IGVM_VHS_PARAMETER_INSERT)); 160 | ins = (IGVM_VHS_PARAMETER_INSERT *)(igvm_get_buffer(igvm, data) + sizeof(IGVM_VHS_VARIABLE_HEADER)); 161 | CU_ASSERT_EQUAL(ins->parameter_area_index, 0); 162 | CU_ASSERT_EQUAL(ins->compatibility_mask, 1); 163 | CU_ASSERT_EQUAL(ins->gpa, 0x14000); 164 | igvm_free_buffer(igvm, data); 165 | 166 | igvm_free(igvm); 167 | } 168 | 169 | void test_associated_data(void) { 170 | IgvmHandle igvm; 171 | uint32_t data_length = 0; 172 | uint32_t i; 173 | int all_same = 1; 174 | IgvmHandle data; 175 | 176 | igvm = igvm_new_from_binary(igvm_buf, igvm_buf_length); 177 | CU_ASSERT(igvm > 0); 178 | data = igvm_get_header_data(igvm, IGVM_HEADER_SECTION_DIRECTIVE, 3); 179 | CU_ASSERT(data > 0); 180 | 181 | data_length = igvm_get_buffer_size(igvm, data); 182 | CU_ASSERT_EQUAL(data_length, 0x1000); 183 | for (i = 0; i < data_length; ++i) { 184 | if (igvm_get_buffer(igvm, data)[i] != 4) { 185 | all_same = 0; 186 | break; 187 | } 188 | } 189 | CU_ASSERT_EQUAL(all_same, 1); 190 | 191 | igvm_free_buffer(igvm, data); 192 | igvm_free(igvm); 193 | } 194 | 195 | void test_no_associated_data(void) { 196 | IgvmHandle igvm; 197 | uint32_t data_length = 0; 198 | 199 | igvm = igvm_new_from_binary(igvm_buf, igvm_buf_length); 200 | CU_ASSERT(igvm > 0); 201 | CU_ASSERT_EQUAL(igvm_get_header_data(igvm, IGVM_HEADER_SECTION_DIRECTIVE, 9), IGVMAPI_NO_DATA); 202 | 203 | igvm_free(igvm); 204 | } 205 | 206 | static int parse_args(int argc, char **argv) { 207 | int i; 208 | for (i = 1; i < argc; ++i) { 209 | if (argv[i][0] != '-') { 210 | if (filename != NULL) { 211 | printf("Invalid command line\n"); 212 | return 1; 213 | } 214 | filename = argv[i]; 215 | } 216 | else { 217 | printf("Invalid argument: %s\n", argv[i]); 218 | return 1; 219 | } 220 | } 221 | if (!filename) { 222 | printf("Filename not provided\n"); 223 | return 1; 224 | } 225 | return 0; 226 | } 227 | 228 | int main(int argc, char **argv) { 229 | FILE *fp; 230 | int failed = 0; 231 | 232 | if (parse_args(argc, argv)) { 233 | return 1; 234 | } 235 | 236 | fp = fopen(filename, "rb"); 237 | if (!fp) { 238 | printf("Could not open file\n"); 239 | return 1; 240 | } 241 | fseek(fp, 0, SEEK_END); 242 | igvm_buf_length = ftell(fp); 243 | fseek(fp, 0, SEEK_SET); 244 | 245 | igvm_buf = (uint8_t *)malloc(igvm_buf_length); 246 | if (!igvm_buf) { 247 | fclose(fp); 248 | printf("Could not allocate buffer to read file\n"); 249 | return 1; 250 | } 251 | if (fread(igvm_buf, 1, igvm_buf_length, fp) != igvm_buf_length) { 252 | fclose(fp); 253 | free(igvm_buf); 254 | printf("Failed to read file\n"); 255 | return 1; 256 | } 257 | fclose(fp); 258 | 259 | CU_pSuite suite = NULL; 260 | if (CU_initialize_registry() != CUE_SUCCESS) { 261 | return -1; 262 | } 263 | 264 | suite = CU_add_suite("igvm", NULL, NULL); 265 | if (!suite) { 266 | return -1; 267 | } 268 | 269 | CU_add_test(suite, "Parse valid IGVM file", test_valid_igvm); 270 | CU_add_test(suite, "Parse invalid fixed header", test_invalid_fixed_header); 271 | CU_add_test(suite, "Check header counting", test_header_counts); 272 | CU_add_test(suite, "Test for a valid platform header", test_platform_header); 273 | CU_add_test(suite, "Test for valid initialization headers", test_initialization_header); 274 | CU_add_test(suite, "Test for valid directive headers", test_directive_header); 275 | CU_add_test(suite, "Test for valid associated data", test_associated_data); 276 | CU_add_test(suite, "Test for no associated data", test_no_associated_data); 277 | 278 | CU_basic_set_mode(CU_BRM_VERBOSE); 279 | CU_basic_run_tests(); 280 | failed = CU_get_number_of_tests_failed(); 281 | CU_cleanup_registry(); 282 | free(igvm_buf); 283 | 284 | return (failed > 0) ? 1 : 0; 285 | } -------------------------------------------------------------------------------- /igvm_defs/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Corporation. 2 | # Licensed under the MIT License. 3 | 4 | [package] 5 | name = "igvm_defs" 6 | version = "0.3.4" 7 | edition = "2021" 8 | description = "The igvm_defs crate is the specification for the Independent Guest Virtual Machine (IGVM) file format." 9 | license = "MIT" 10 | authors = ["Microsoft"] 11 | repository = "https://github.com/microsoft/igvm" 12 | keywords = ["virtualization"] 13 | categories = ["virtualization", "no-std"] 14 | 15 | [package.metadata.docs.rs] 16 | # Document all features 17 | all-features = true 18 | # Defines the configuration attribute `docsrs` which emits nicer docs via 19 | # nightly features. 20 | # 21 | # Run locally with RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features 22 | rustdoc-args = ["--cfg", "docsrs"] 23 | 24 | [features] 25 | default = [] 26 | unstable = [] # For types that are not yet stabilized in an official version 27 | 28 | [dependencies] 29 | bitfield-struct.workspace = true 30 | open-enum.workspace = true 31 | static_assertions.workspace = true 32 | zerocopy.workspace = true 33 | -------------------------------------------------------------------------------- /igvm_defs/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /igvm_defs/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /igvm_defs/src/dt.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // 3 | // Copyright (c) Microsoft Corporation. 4 | 5 | //! Device tree (DT) specific information related to IGVM. 6 | 7 | /// The property name to describe IGVM type specific information on a DT node. 8 | /// 9 | /// A DT memory node is extended with the IGVM type property to describe the 10 | /// IGVM memory type for that node. This is encoded as a u32 value containing 11 | /// the type defined by [`crate::MemoryMapEntryType`]. 12 | pub const IGVM_DT_IGVM_TYPE_PROPERTY: &str = "microsoft,igvm-type"; 13 | 14 | /// The property name to describe VTL specific information on a DT node. 15 | /// 16 | /// A DT VMBUS root node is extended with the VTL property to describe the VTL 17 | /// this root node is for. VTL is encoded as a u32 value. 18 | pub const IGVM_DT_VTL_PROPERTY: &str = "microsoft,vtl"; 19 | --------------------------------------------------------------------------------