Skip to content

Commit 541f72a

Browse files
committed
Add tdx measurement crate.
This CR simply sets up the crate and migrate the Yu's implementation without much functionality change. It serves as the first version and more changes will be in follow up CRs. BUG: 393394590 Change-Id: Ia23a8536c8101198ace5f1202177b168d4db245d
1 parent b200dda commit 541f72a

File tree

4 files changed

+251
-0
lines changed

4 files changed

+251
-0
lines changed

WORKSPACE

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,17 @@ http_archive(
333333
url = "https://storage.googleapis.com/oak-bins/sysroot/" + SYSROOT_SHA256 + ".tar.xz",
334334
)
335335

336+
# The binary is used for stage0 tdx measurement test
337+
# (//tdx_measurement:tdx_measurement_test) only.
338+
STAGE0_BIN_TDX_COMMIT = "0689771e6fd6d174121eaa0b7df5fe54c4746ce3"
339+
340+
http_file(
341+
name = "stage0_tdx_bin_for_test",
342+
downloaded_file_path = "stage0_tdx_bin_for_test",
343+
sha256 = "87fe23ad59066718f97acfe2672f70e6ddfa488f7593d59b8886f67d0ca08715",
344+
url = "https://storage.googleapis.com/oak-bins/binary/" + STAGE0_BIN_TDX_COMMIT + "/stage0_bin_tdx/binary",
345+
)
346+
336347
http_file(
337348
name = "oak_containers_system_image_base",
338349
downloaded_file_path = "base-image.tar.xz",

tdx_measurement/BUILD

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_test")
18+
19+
package(
20+
default_visibility = ["//:internal"],
21+
licenses = ["notice"],
22+
)
23+
24+
# Example:
25+
# bazel run //tdx_measurement -- \
26+
# --stage0-rom=$(pwd)/artifacts/stage0_bin_tdx
27+
rust_binary(
28+
name = "tdx_measurement",
29+
srcs = glob(["src/**"]),
30+
crate_root = "src/main.rs",
31+
deps = [
32+
"@oak_crates_index//:anyhow",
33+
"@oak_crates_index//:clap",
34+
"@oak_crates_index//:env_logger",
35+
"@oak_crates_index//:hex",
36+
"@oak_crates_index//:log",
37+
"@oak_crates_index//:sha2",
38+
"@oak_crates_index//:static_assertions",
39+
"@oak_crates_index//:strum",
40+
"@oak_crates_index//:x86_64",
41+
"@oak_crates_index//:zerocopy",
42+
],
43+
)
44+
45+
rust_test(
46+
name = "tdx_measurement_test",
47+
crate = ":tdx_measurement",
48+
data = ["@stage0_tdx_bin_for_test//file:stage0_tdx_bin_for_test"],
49+
deps = ["//oak_file_utils"],
50+
)

tdx_measurement/src/main.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
mod measure;
18+
use std::path::PathBuf;
19+
20+
use clap::Parser;
21+
use hex::ToHex;
22+
23+
#[derive(Parser, Clone, Debug)]
24+
#[command(about = "Oak Tdx Measurement Calculator")]
25+
struct Cli {
26+
#[arg(long, help = "The location of the Stage 0 firmware ROM image")]
27+
stage0_rom: PathBuf,
28+
}
29+
30+
fn main() -> anyhow::Result<()> {
31+
let cli = Cli::parse();
32+
33+
let stage0_bin: Vec<u8> = std::fs::read(cli.stage0_rom).unwrap();
34+
println!("{}", measure::mr_td_measurement(&stage0_bin).encode_hex::<String>());
35+
36+
Ok(())
37+
}

tdx_measurement/src/measure.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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

Comments
 (0)