Skip to content

Commit 143ab03

Browse files
committed
vtpm: Add TPM internal state extract/inject via libtcgtpm accessors
Adds VtpmInternalState plus extract_vtpm_state() / inject_vtpm_state() that call into the C-side accessors exposed by libtcgtpm (state_accessor.{c,h}, commit 1) to read and write the TPM 2.0 Reference Implementation's persistent globals (gp, gc, gr). A bulk-serialization path is also wired up: * extract_serialized_state() / inject_serialized_state() round-trip the entire set of accessor fields as one opaque buffer, which is what the seal/unseal flow stores inside a SealedBlob. * A command-based fallback (extract_vtpm_state_via_commands) is sketched for environments where the direct accessors are unavailable. This is the bridge that lets the Rust persistence layer recover a manufactured vTPM byte-for-byte after a cold boot. Signed-off-by: Goodleon-Y <goodleon3@126.com>
1 parent 72f12ef commit 143ab03

2 files changed

Lines changed: 321 additions & 0 deletions

File tree

kernel/src/vtpm/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub mod proxy;
1515
pub mod sealed;
1616
/// Pluggable storage backend for the sealed vTPM blob.
1717
pub mod sealed_store;
18+
/// TPM internal-state extraction and injection via libtcgtpm accessors.
19+
pub mod state;
1820
/// TPM 2.0 Reference Implementation
1921
pub mod tcgtpm;
2022

kernel/src/vtpm/state.rs

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
// SPDX-License-Identifier: MIT
2+
//
3+
// TPM internal-state extraction and injection.
4+
//
5+
// Provides `extract_vtpm_state()` / `inject_vtpm_state()` via FFI into the
6+
// `state_accessor.{c,h}` getter/setter functions, which directly access
7+
// the TPM 2.0 Reference Implementation's internal globals (gp, gc, gr).
8+
// A command-based fallback (`extract_vtpm_state_via_commands`) is sketched
9+
// for environments where the direct accessors are unavailable.
10+
11+
extern crate alloc;
12+
13+
use alloc::vec;
14+
use alloc::vec::Vec;
15+
16+
use crate::protocols::errors::SvsmReqError;
17+
18+
// ============================================================
19+
// FFI Declarations — direct extern "C" (no bindgen needed)
20+
// ============================================================
21+
22+
unsafe extern "C" {
23+
// Seed accessors
24+
fn get_ep_seed(out: *mut u8);
25+
fn set_ep_seed(in_: *const u8);
26+
fn get_sp_seed(out: *mut u8);
27+
fn set_sp_seed(in_: *const u8);
28+
fn get_pp_seed(out: *mut u8);
29+
fn set_pp_seed(in_: *const u8);
30+
31+
// Auth value accessors (return actual size written)
32+
fn get_owner_auth(out_buf: *mut u8, buf_size: usize) -> usize;
33+
fn set_owner_auth(in_: *const u8, len: usize);
34+
fn get_endorsement_auth(out_buf: *mut u8, buf_size: usize) -> usize;
35+
fn set_endorsement_auth(in_: *const u8, len: usize);
36+
fn get_lockout_auth(out_buf: *mut u8, buf_size: usize) -> usize;
37+
fn set_lockout_auth(in_: *const u8, len: usize);
38+
fn get_platform_auth(out_buf: *mut u8, buf_size: usize) -> usize;
39+
fn set_platform_auth(in_: *const u8, len: usize);
40+
41+
// Counter accessors
42+
fn get_total_reset_count() -> u64;
43+
fn set_total_reset_count(val: u64);
44+
fn get_reset_count() -> u32;
45+
fn set_reset_count(val: u32);
46+
fn get_clear_count() -> u32;
47+
fn set_clear_count(val: u32);
48+
#[allow(dead_code)]
49+
fn get_object_context_id() -> u64;
50+
#[allow(dead_code)]
51+
fn set_object_context_id(val: u64);
52+
53+
// PCR save accessors
54+
fn get_pcr_save(out_buf: *mut u8, buf_size: usize) -> usize;
55+
fn set_pcr_save(in_: *const u8, len: usize);
56+
57+
// Bulk serialization
58+
fn serialize_vtpm_state(out_buf: *mut u8, buf_size: usize) -> usize;
59+
fn deserialize_vtpm_state(in_: *const u8, len: usize) -> i32;
60+
}
61+
62+
// ============================================================
63+
// Constants
64+
// ============================================================
65+
66+
/// Primary seed size in bytes (SHA-256 → 32 bytes)
67+
pub const PRIMARY_SEED_SIZE: usize = 32;
68+
69+
/// Maximum auth value buffer size
70+
const MAX_AUTH_SIZE: usize = 64;
71+
72+
/// Maximum PCR save area size (worst case: SHA-256 + SHA-384 + SHA-512 banks)
73+
const MAX_PCR_SAVE_SIZE: usize = 4096;
74+
75+
/// Maximum serialized state buffer size
76+
const MAX_SERIALIZED_SIZE: usize = 8192;
77+
78+
// ============================================================
79+
// VtpmInternalState — Mirror of TPM Internal Globals
80+
// ============================================================
81+
82+
/// Complete dump of seal-relevant TPM internal state.
83+
///
84+
/// Covers Persistent (gp), State Clear (gc), and State Reset (gr) data.
85+
#[derive(Debug, Clone)]
86+
pub struct VtpmInternalState {
87+
/// Endorsement Primary Seed (32 bytes)
88+
pub ep_seed: [u8; 32],
89+
/// Storage Primary Seed (32 bytes) — the SRK seed
90+
pub sp_seed: [u8; 32],
91+
/// Platform Primary Seed (32 bytes)
92+
pub pp_seed: [u8; 32],
93+
/// Owner hierarchy auth value
94+
pub owner_auth: Vec<u8>,
95+
/// Endorsement hierarchy auth value
96+
pub endorsement_auth: Vec<u8>,
97+
/// Lockout auth value
98+
pub lockout_auth: Vec<u8>,
99+
/// Platform auth value
100+
pub platform_auth: Vec<u8>,
101+
/// PCR save area (raw binary dump of PCR_SAVE struct)
102+
pub pcr_save: Vec<u8>,
103+
/// Total reset counter (monotonic across TPM lifetime)
104+
pub total_reset_count: u64,
105+
/// Reset counter (reset by TPM2_Clear)
106+
pub reset_count: u32,
107+
/// Clear count (incremented on TPM Resume)
108+
pub clear_count: u32,
109+
}
110+
111+
impl VtpmInternalState {
112+
/// Create an empty (all-zeros) state for bootstrap.
113+
pub fn empty() -> Self {
114+
Self {
115+
ep_seed: [0u8; 32],
116+
sp_seed: [0u8; 32],
117+
pp_seed: [0u8; 32],
118+
owner_auth: Vec::new(),
119+
endorsement_auth: Vec::new(),
120+
lockout_auth: Vec::new(),
121+
platform_auth: Vec::new(),
122+
pcr_save: Vec::new(),
123+
total_reset_count: 0,
124+
reset_count: 0,
125+
clear_count: 0,
126+
}
127+
}
128+
}
129+
130+
// ============================================================
131+
// Direct global access via libtcgtpm accessors (FFI)
132+
// ============================================================
133+
134+
/// Extract all seal-relevant TPM internal state via direct C global access.
135+
///
136+
/// Calls the state_accessor.c getter functions, which read gp, gc, gr
137+
/// directly. This is the fastest path but requires libtcgtpm to be
138+
/// compiled with state_accessor.c.
139+
pub fn extract_vtpm_state() -> Result<VtpmInternalState, SvsmReqError> {
140+
let mut ep_seed = [0u8; 32];
141+
let mut sp_seed = [0u8; 32];
142+
let mut pp_seed = [0u8; 32];
143+
144+
// SAFETY: FFI calls into state_accessor.c. Buffer pointers and sizes are correct.
145+
unsafe {
146+
get_ep_seed(ep_seed.as_mut_ptr());
147+
get_sp_seed(sp_seed.as_mut_ptr());
148+
get_pp_seed(pp_seed.as_mut_ptr());
149+
}
150+
151+
let owner_auth = get_auth_value(get_owner_auth)?;
152+
let endorsement_auth = get_auth_value(get_endorsement_auth)?;
153+
let lockout_auth = get_auth_value(get_lockout_auth)?;
154+
let platform_auth = get_auth_value(get_platform_auth)?;
155+
156+
let mut pcr_buf = vec![0u8; MAX_PCR_SAVE_SIZE];
157+
let pcr_len;
158+
// SAFETY: FFI call with correct buffer size.
159+
unsafe {
160+
pcr_len = get_pcr_save(pcr_buf.as_mut_ptr(), pcr_buf.len());
161+
}
162+
pcr_buf.truncate(pcr_len);
163+
164+
let total_reset_count;
165+
let reset_count;
166+
let clear_count;
167+
// SAFETY: FFI calls returning scalar values.
168+
unsafe {
169+
total_reset_count = get_total_reset_count();
170+
reset_count = get_reset_count();
171+
clear_count = get_clear_count();
172+
}
173+
174+
Ok(VtpmInternalState {
175+
ep_seed,
176+
sp_seed,
177+
pp_seed,
178+
owner_auth,
179+
endorsement_auth,
180+
lockout_auth,
181+
platform_auth,
182+
pcr_save: pcr_buf,
183+
total_reset_count,
184+
reset_count,
185+
clear_count,
186+
})
187+
}
188+
189+
/// Inject TPM internal state via direct C global write.
190+
///
191+
/// Writes all seal-relevant fields back into gp, gc, gr. This must be
192+
/// called before any TPM operation that depends on the restored state
193+
/// (e.g., before creating the SRK or loading persistent objects).
194+
pub fn inject_vtpm_state(state: &VtpmInternalState) -> Result<(), SvsmReqError> {
195+
// SAFETY: FFI calls into state_accessor.c. All pointers and lengths are valid.
196+
unsafe {
197+
set_ep_seed(state.ep_seed.as_ptr());
198+
set_sp_seed(state.sp_seed.as_ptr());
199+
set_pp_seed(state.pp_seed.as_ptr());
200+
201+
set_owner_auth(state.owner_auth.as_ptr(), state.owner_auth.len());
202+
set_endorsement_auth(
203+
state.endorsement_auth.as_ptr(),
204+
state.endorsement_auth.len(),
205+
);
206+
set_lockout_auth(state.lockout_auth.as_ptr(), state.lockout_auth.len());
207+
set_platform_auth(state.platform_auth.as_ptr(), state.platform_auth.len());
208+
209+
set_pcr_save(state.pcr_save.as_ptr(), state.pcr_save.len());
210+
211+
set_total_reset_count(state.total_reset_count);
212+
set_reset_count(state.reset_count);
213+
set_clear_count(state.clear_count);
214+
}
215+
216+
Ok(())
217+
}
218+
219+
// ============================================================
220+
// Bulk Serialization (C-backed)
221+
// ============================================================
222+
223+
/// Serialize all seal-relevant state into a flat binary buffer using
224+
/// the C `serialize_vtpm_state()` function.
225+
///
226+
/// Returns the serialized bytes. This is the recommended path for
227+
/// state extraction before sealing, as the C function knows the exact
228+
/// struct layouts.
229+
pub fn extract_serialized_state() -> Result<Vec<u8>, SvsmReqError> {
230+
let mut buf = vec![0u8; MAX_SERIALIZED_SIZE];
231+
let written;
232+
// SAFETY: FFI call with correctly sized buffer.
233+
unsafe {
234+
written = serialize_vtpm_state(buf.as_mut_ptr(), buf.len());
235+
}
236+
if written == 0 || written > buf.len() {
237+
log::error!("serialize_vtpm_state failed: buffer too small or error");
238+
return Err(SvsmReqError::invalid_request());
239+
}
240+
buf.truncate(written);
241+
Ok(buf)
242+
}
243+
244+
/// Deserialize state from the C `serialize_vtpm_state()` format back
245+
/// into TPM globals.
246+
pub fn inject_serialized_state(data: &[u8]) -> Result<(), SvsmReqError> {
247+
let ret;
248+
// SAFETY: FFI call with valid buffer pointer and length.
249+
unsafe {
250+
ret = deserialize_vtpm_state(data.as_ptr(), data.len());
251+
}
252+
if ret != 0 {
253+
log::error!("deserialize_vtpm_state failed: error code {ret}");
254+
return Err(SvsmReqError::invalid_request());
255+
}
256+
Ok(())
257+
}
258+
259+
// ============================================================
260+
// Command-based fallback path (skeleton)
261+
// ============================================================
262+
263+
/// Extract vTPM state using standard TPM 2.0 commands.
264+
///
265+
/// This path uses:
266+
/// - TPM2_NV_Read for persistent NV indices (auth values, counters)
267+
/// - TPM2_ContextSave for loaded objects (EK, SRK)
268+
/// - TPM2_GetCapability for PCR allocation and algorithm info
269+
///
270+
/// This is specification-compliant and works with any TPM (real or
271+
/// simulated), but requires the TPM to be fully initialized and the
272+
/// target objects to be loaded.
273+
///
274+
/// NOT YET IMPLEMENTED — placeholder for environments where the direct
275+
/// libtcgtpm accessors are unavailable.
276+
#[allow(dead_code)]
277+
pub fn extract_vtpm_state_via_commands() -> Result<Vec<u8>, SvsmReqError> {
278+
// A command-based extraction path would:
279+
// 1. TPM2_HierarchyChangeAuth(TPM_RH_OWNER) — read ownerAuth
280+
// (can't actually read it; would need prior knowledge or use Policy)
281+
// 2. TPM2_NV_Read for NV indices 0x01c00000–0x01c00003 (EK, SRK templates)
282+
// 3. TPM2_ContextSave for the SRK handle (if loaded)
283+
// 4. TPM2_ContextSave for the EK handle (if loaded)
284+
// 5. TPM2_GetCapability(TPM_CAP_TPM_PROPERTIES, TPM_PT_RESET_COUNT, ...)
285+
// 6. TPM2_GetCapability(TPM_CAP_PCR, ...)
286+
//
287+
// Limitation: auth values cannot be read via TPM commands by design.
288+
// This path therefore requires the provisioner to have set known auth
289+
// values during manufacturing, or to use policy-based access.
290+
log::warn!("Command-based state extraction not yet implemented");
291+
Err(SvsmReqError::invalid_request())
292+
}
293+
294+
// ============================================================
295+
// Helpers
296+
// ============================================================
297+
298+
/// Read an auth value via the C getter, which returns the actual
299+
/// length and copies data into a caller-provided buffer.
300+
fn get_auth_value(
301+
getter: unsafe extern "C" fn(*mut u8, usize) -> usize,
302+
) -> Result<Vec<u8>, SvsmReqError> {
303+
let mut buf = vec![0u8; MAX_AUTH_SIZE];
304+
let len;
305+
// SAFETY: FFI call into state_accessor.c. Buffer pointer and size are valid.
306+
unsafe {
307+
len = getter(buf.as_mut_ptr(), buf.len());
308+
}
309+
if len > buf.len() {
310+
log::error!(
311+
"Auth value getter returned length {} > buffer {}",
312+
len,
313+
buf.len()
314+
);
315+
return Err(SvsmReqError::invalid_request());
316+
}
317+
buf.truncate(len);
318+
Ok(buf)
319+
}

0 commit comments

Comments
 (0)