|
| 1 | +// |
| 2 | +// Copyright 2025 The Project Oak Authors |
| 3 | +// |
| 4 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +// you may not use this file except in compliance with the License. |
| 6 | +// You may obtain a copy of the License at |
| 7 | +// |
| 8 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +// |
| 10 | +// Unless required by applicable law or agreed to in writing, software |
| 11 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +// See the License for the specific language governing permissions and |
| 14 | +// limitations under the License. |
| 15 | +// |
| 16 | + |
| 17 | +use sha2::{Digest, Sha384}; |
| 18 | + |
| 19 | +const MEM_PAGE_ADD_HEADER: &[u8] = b"MEM.PAGE.ADD"; |
| 20 | +const MR_EXTEND_HEADER: &[u8] = b"MR.EXTEND"; |
| 21 | +const PAGE_SIZE: u64 = 0x1000; |
| 22 | +const CHUNK_SIZE: usize = 256; |
| 23 | +const EXTENSION_BUFFER_SIZE: usize = 128; |
| 24 | + |
| 25 | +// Intel TDX Application Binary Interface (ABI) Reference Draft 1.5.05.44 |
| 26 | +// 5.3.20. TDH.MEM.PAGE.ADD Leaf |
| 27 | +// |
| 28 | +// Extension is done using SHA384 with a 128B extension buffer composed |
| 29 | +// as follows: |
| 30 | +// * Bytes 0 through 11 contain the ASCII string “MEM.PAGE.ADD”. |
| 31 | +// * Bytes 16 through 23 contain the GPA (in little-endian format). |
| 32 | +// * All the other bytes contain 0. |
| 33 | +fn add_page(addr: u64) -> [u8; EXTENSION_BUFFER_SIZE] { |
| 34 | + let mut buf = [0; EXTENSION_BUFFER_SIZE]; |
| 35 | + buf[..MEM_PAGE_ADD_HEADER.len()].copy_from_slice(MEM_PAGE_ADD_HEADER); |
| 36 | + buf[16..24].copy_from_slice(&addr.to_le_bytes()); |
| 37 | + buf |
| 38 | +} |
| 39 | + |
| 40 | +// Intel TDX Application Binary Interface (ABI) Reference Draft 1.5.05.44 |
| 41 | +// 5.3.44. TDH.MR.EXTEND Leaf |
| 42 | +// |
| 43 | +// Extend TDCS.MRTD with the chunk’s GPA and contents. Extension is done |
| 44 | +// using SHA384, with three 128B extension buffers. The first extension |
| 45 | +// buffer is composed as follows: |
| 46 | +// * Bytes 0 through 8 contain the ASCII string “MR.EXTEND”. |
| 47 | +// * Bytes 16 through 23 contain the GPA (in little-endian format). |
| 48 | +// * All the other bytes contain 0. |
| 49 | +// The other two extension buffers contain the chunk’s contents. |
| 50 | +fn extend_256_byte( |
| 51 | + addr: u64, |
| 52 | + chunk: &[u8], |
| 53 | +) -> ([u8; EXTENSION_BUFFER_SIZE], [u8; EXTENSION_BUFFER_SIZE], [u8; EXTENSION_BUFFER_SIZE]) { |
| 54 | + let mut buf1 = [0; EXTENSION_BUFFER_SIZE]; |
| 55 | + buf1[..MR_EXTEND_HEADER.len()].copy_from_slice(MR_EXTEND_HEADER); |
| 56 | + buf1[16..24].copy_from_slice(&addr.to_le_bytes()); |
| 57 | + |
| 58 | + let mut buf2 = [0; EXTENSION_BUFFER_SIZE]; |
| 59 | + buf2.copy_from_slice(&chunk[..EXTENSION_BUFFER_SIZE]); |
| 60 | + |
| 61 | + let mut buf3 = [0; EXTENSION_BUFFER_SIZE]; |
| 62 | + buf3.copy_from_slice(&chunk[EXTENSION_BUFFER_SIZE..CHUNK_SIZE]); |
| 63 | + |
| 64 | + (buf1, buf2, buf3) |
| 65 | +} |
| 66 | + |
| 67 | +/// Calculates the MR_TD measurement for a TD's initial state. |
| 68 | +/// |
| 69 | +/// This function simulates the SHA384 measurement of the TD's initial state by |
| 70 | +/// recording the sequence of MEM.PAGE.ADD and MR.EXTEND seamcalls. The order of |
| 71 | +/// these calls is crucial for the measurement. |
| 72 | +/// |
| 73 | +/// The algorithm operates on a per-section basis within the firmware. For each |
| 74 | +/// section, it performs the following steps: |
| 75 | +/// |
| 76 | +/// 1. **Page Addition:** Adds blank 4KB pages to the TD using MEM.PAGE.ADD, |
| 77 | +/// corresponding to the section's size and starting address. |
| 78 | +/// 2. **Content Measurement (MR.EXTEND):** If the section has the "MR.EXTEND" |
| 79 | +/// attribute set, it measures the content of the section in 256-byte chunks |
| 80 | +/// using MR.EXTEND. This is done by iterating over the section's memory and |
| 81 | +/// making a separate MR.EXTEND call for each chunk. |
| 82 | +/// |
| 83 | +/// In Oak, the `stage0_bin_tdx` has the following four sections: |
| 84 | +/// |
| 85 | +/// * TD_HOB: 2 pages, starting at 0x100000 |
| 86 | +/// * Mailbox: 1 page, starting at 0x102000 |
| 87 | +/// * Binary Firmware Volume (BFV): 512 pages, starting at 0xffe00000 |
| 88 | +/// * Ram low: 160 pages, starting at 0x0 |
| 89 | +/// |
| 90 | +/// Only the BFV section has the MR.EXTEND attribute. Therefore, the TDX module |
| 91 | +/// performs the following sequence of actions: |
| 92 | +/// |
| 93 | +/// ``` |
| 94 | +/// MEM.PAGE.ADD(0x100000) |
| 95 | +/// MEM.PAGE.ADD(0x101000) |
| 96 | +/// MEM.PAGE.ADD(0x102000) |
| 97 | +/// MEM.PAGE.ADD(0xFFE00000 … 0xFFFF0000) // 512 pages |
| 98 | +/// MR.EXTEND(0xFFE00000.. 0xFFFFFF00) // 256B chunk x 8192 times |
| 99 | +/// MEM.PAGE.ADD(0x0..0x9F000) // 160 pages |
| 100 | +/// ``` |
| 101 | +/// |
| 102 | +/// The function effectively reproduces this sequence to calculate the MR_TD |
| 103 | +/// measurement. |
| 104 | +pub fn mr_td_measurement(stage0_bin: &[u8]) -> Vec<u8> { |
| 105 | + assert_eq!(stage0_bin.len(), 2 * 1024 * 1024); |
| 106 | + |
| 107 | + let mut hasher = Sha384::new(); |
| 108 | + |
| 109 | + hasher.update(add_page(0x10_0000)); |
| 110 | + hasher.update(add_page(0x10_1000)); |
| 111 | + hasher.update(add_page(0x10_2000)); |
| 112 | + |
| 113 | + for i in 0..512 { |
| 114 | + hasher.update(add_page(0xffe0_0000 + 0x1000 * i)); |
| 115 | + } |
| 116 | + |
| 117 | + for i in 0..8192 { |
| 118 | + let chunk_offset = i * CHUNK_SIZE; |
| 119 | + let chunk = &stage0_bin[chunk_offset..chunk_offset + CHUNK_SIZE]; |
| 120 | + let (buf1, buf2, buf3) = extend_256_byte(0xffe0_0000 + chunk_offset as u64, chunk); |
| 121 | + hasher.update(buf1); |
| 122 | + hasher.update(buf2); |
| 123 | + hasher.update(buf3); |
| 124 | + } |
| 125 | + |
| 126 | + for i in 0..160 { |
| 127 | + hasher.update(add_page(PAGE_SIZE * i)); |
| 128 | + } |
| 129 | + |
| 130 | + let result = hasher.finalize(); |
| 131 | + |
| 132 | + result.as_slice().into() |
| 133 | +} |
| 134 | + |
| 135 | +#[cfg(test)] |
| 136 | +mod tests { |
| 137 | + use hex::ToHex; |
| 138 | + use oak_file_utils::data_path; |
| 139 | + |
| 140 | + use super::*; |
| 141 | + |
| 142 | + #[test] |
| 143 | + fn test_mr_td_is_measured_correctly() { |
| 144 | + const STAGE0_BIN_PATH: &str = |
| 145 | + "external/stage0_tdx_bin_for_test/file/stage0_tdx_bin_for_test"; |
| 146 | + let stage0_bin = std::fs::read(data_path(STAGE0_BIN_PATH)).unwrap(); |
| 147 | + let mr_td = mr_td_measurement(&stage0_bin); |
| 148 | + assert_eq!(mr_td.len(), 48); |
| 149 | + let expected_hash_str = "7e63acc88a8870e33957754f12913d7a533178e171c26e58b91f6674ecb5e091b76d0cd742e703f97d7c54451e64fd00"; |
| 150 | + let actual_hash_str = mr_td.encode_hex::<String>().to_string(); |
| 151 | + assert_eq!(actual_hash_str, expected_hash_str); |
| 152 | + } |
| 153 | +} |
0 commit comments