Skip to content

Commit e9614b7

Browse files
authored
Merge pull request #228 from Dstack-TEE/dstack-mr
ACPI Tables Dynamic Generation
2 parents 71a6906 + 3745453 commit e9614b7

20 files changed

Lines changed: 1602 additions & 106 deletions

File tree

Cargo.lock

Lines changed: 154 additions & 71 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ members = [
4040
"sdk/rust",
4141
"sodiumbox",
4242
"serde-duration",
43+
"dstack-mr",
44+
"dstack-mr/cli",
4345
]
4446
resolver = "2"
4547

@@ -68,6 +70,7 @@ cert-client = { path = "cert-client" }
6870
lspci = { path = "lspci" }
6971
sodiumbox = { path = "sodiumbox" }
7072
serde-duration = { path = "serde-duration" }
73+
dstack-mr = { path = "dstack-mr" }
7174

7275
# Core dependencies
7376
anyhow = "1.0.97"
@@ -94,6 +97,7 @@ bon = "3.4.0"
9497
base64 = "0.22.1"
9598
hex = "0.4.3"
9699
hex_fmt = "0.3.0"
100+
hex-literal = "1.0.0"
97101
prost = "0.13.5"
98102
scale = { version = "3.7.4", package = "parity-scale-codec", features = ["derive"] }
99103
serde = { version = "1.0.219", features = ["derive"] }
@@ -104,6 +108,7 @@ toml_edit = { version = "0.22.24", features = ["serde"] }
104108
yasna = "0.5.2"
105109
bytes = "1.10.1"
106110
figment = "0.10.19"
111+
object = "0.36.4"
107112

108113
# Networking/HTTP
109114
bollard = "0.18.1"
@@ -118,7 +123,7 @@ rocket = { git = "https://github.com/rwf2/Rocket", branch = "master", features =
118123
rocket-apitoken = { git = "https://github.com/kvinwang/rocket-apitoken", branch = "dev" }
119124
tokio = { version = "1.44.1" }
120125
tokio-vsock = "0.7.0"
121-
sysinfo = "0.33.1"
126+
sysinfo = "0.35.2"
122127
default-net = "0.22.0"
123128

124129
# Cryptography/Security

dstack-mr/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/target

dstack-mr/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "dstack-mr"
3+
version.workspace = true
4+
authors.workspace = true
5+
edition.workspace = true
6+
license.workspace = true
7+
description = "A CLI tool for calculating TDX measurements for DStack images"
8+
9+
[dependencies]
10+
serde = { workspace = true, features = ["derive"] }
11+
serde_json.workspace = true
12+
serde-human-bytes.workspace = true
13+
hex.workspace = true
14+
thiserror.workspace = true
15+
sha2.workspace = true
16+
anyhow.workspace = true
17+
object.workspace = true
18+
hex-literal.workspace = true
19+
fs-err.workspace = true
20+
bon.workspace = true
21+
log.workspace = true

dstack-mr/cli/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "dstack-mr-cli"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[[bin]]
7+
name = "dstack-mr"
8+
path = "src/main.rs"
9+
10+
[dependencies]
11+
clap.workspace = true
12+
dstack-mr.workspace = true
13+
anyhow.workspace = true
14+
hex.workspace = true
15+
dstack-types.workspace = true
16+
fs-err.workspace = true
17+
serde_json.workspace = true
18+
tracing-subscriber.workspace = true

dstack-mr/cli/src/main.rs

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
use anyhow::{Context, Result, anyhow};
2+
use clap::{Parser, Subcommand};
3+
use dstack_mr::Machine;
4+
use dstack_types::ImageInfo;
5+
use fs_err as fs;
6+
use std::path::PathBuf;
7+
8+
#[derive(Parser)]
9+
#[command(author, version, about, long_about = None)]
10+
struct Cli {
11+
#[command(subcommand)]
12+
command: Commands,
13+
}
14+
15+
#[derive(Subcommand)]
16+
enum Commands {
17+
/// Measure a machine configuration
18+
Measure(MachineConfig),
19+
}
20+
21+
type Bool = bool;
22+
23+
#[derive(Parser)]
24+
struct MachineConfig {
25+
/// Number of CPUs
26+
#[arg(short, long, default_value = "1")]
27+
cpu: u8,
28+
29+
/// Memory size in bytes
30+
#[arg(short, long, default_value = "2G", value_parser = parse_memory_size)]
31+
memory: u64,
32+
33+
/// Path to DStack image metadata.json
34+
metadata: PathBuf,
35+
36+
/// Enable two-pass add pages
37+
#[arg(long, default_value = "true")]
38+
two_pass_add_pages: Bool,
39+
40+
/// Enable PIC
41+
#[arg(long, default_value = "true")]
42+
pic: Bool,
43+
44+
/// Enable SMM
45+
#[arg(long, default_value = "false")]
46+
smm: Bool,
47+
48+
/// PCI hole64 size (accepts decimal or hex with 0x prefix)
49+
#[arg(long, value_parser = parse_memory_size)]
50+
pci_hole64_size: Option<u64>,
51+
52+
/// Enable hugepages
53+
#[arg(long, default_value = "false")]
54+
hugepages: bool,
55+
56+
/// Number of GPUs
57+
#[arg(long, default_value = "0")]
58+
num_gpus: u32,
59+
60+
/// Number of NVSwitches
61+
#[arg(long, default_value = "0")]
62+
num_nvswitches: u32,
63+
64+
/// Disable hotplug
65+
#[arg(long, default_value = "false")]
66+
hotplug_off: Bool,
67+
68+
/// Enable root verity
69+
#[arg(long, default_value = "true")]
70+
root_verity: Bool,
71+
72+
/// Output JSON
73+
#[arg(long)]
74+
json: bool,
75+
}
76+
77+
fn main() -> Result<()> {
78+
tracing_subscriber::fmt::init();
79+
80+
let cli = Cli::parse();
81+
match &cli.command {
82+
Commands::Measure(config) => {
83+
let metadata =
84+
fs::read_to_string(&config.metadata).context("Failed to read image metadata")?;
85+
let image_info: ImageInfo =
86+
serde_json::from_str(&metadata).context("Failed to parse image metadata")?;
87+
let parent_dir = config.metadata.parent().unwrap_or(".".as_ref());
88+
let firmware_path = parent_dir.join(&image_info.bios).display().to_string();
89+
let kernel_path = parent_dir.join(&image_info.kernel).display().to_string();
90+
let initrd_path = parent_dir.join(&image_info.initrd).display().to_string();
91+
let cmdline = image_info.cmdline + " initrd=initrd";
92+
93+
let machine = Machine::builder()
94+
.cpu_count(config.cpu)
95+
.memory_size(config.memory)
96+
.firmware(&firmware_path)
97+
.kernel(&kernel_path)
98+
.initrd(&initrd_path)
99+
.kernel_cmdline(&cmdline)
100+
.two_pass_add_pages(config.two_pass_add_pages)
101+
.pic(config.pic)
102+
.smm(config.smm)
103+
.maybe_pci_hole64_size(config.pci_hole64_size)
104+
.hugepages(config.hugepages)
105+
.num_gpus(config.num_gpus)
106+
.num_nvswitches(config.num_nvswitches)
107+
.hotplug_off(config.hotplug_off)
108+
.root_verity(config.root_verity)
109+
.build();
110+
111+
let measurements = machine
112+
.measure()
113+
.context("Failed to measure machine configuration")?;
114+
115+
if config.json {
116+
println!("{}", serde_json::to_string_pretty(&measurements).unwrap());
117+
} else {
118+
println!("Machine measurements:");
119+
println!("MRTD: {}", hex::encode(measurements.mrtd));
120+
println!("RTMR0: {}", hex::encode(measurements.rtmr0));
121+
println!("RTMR1: {}", hex::encode(measurements.rtmr1));
122+
println!("RTMR2: {}", hex::encode(measurements.rtmr2));
123+
}
124+
}
125+
}
126+
127+
Ok(())
128+
}
129+
130+
/// Parse a memory size value that can be decimal or hexadecimal (with 0x prefix)
131+
fn parse_memory_size(s: &str) -> Result<u64> {
132+
let s = s.trim();
133+
134+
if s.is_empty() {
135+
return Err(anyhow!("Empty memory size"));
136+
}
137+
if s.starts_with("0x") || s.starts_with("0X") {
138+
let hex_str = &s[2..];
139+
return u64::from_str_radix(hex_str, 16)
140+
.map_err(|e| anyhow!("Invalid hexadecimal value: {}", e));
141+
}
142+
143+
if s.chars().all(|c| c.is_ascii_digit()) {
144+
return Ok(s.parse::<u64>()?);
145+
}
146+
let len = s.len();
147+
let (num_part, suffix) = match s.chars().last().unwrap() {
148+
'k' | 'K' => (&s[0..len - 1], 1024u64),
149+
'm' | 'M' => (&s[0..len - 1], 1024u64 * 1024),
150+
'g' | 'G' => (&s[0..len - 1], 1024u64 * 1024 * 1024),
151+
't' | 'T' => (&s[0..len - 1], 1024u64 * 1024 * 1024 * 1024),
152+
_ => return Err(anyhow!("Unknown memory size suffix")),
153+
};
154+
155+
let num = num_part.parse::<u64>()?;
156+
Ok(num * suffix)
157+
}

0 commit comments

Comments
 (0)