Skip to content

Commit 031a4a5

Browse files
authored
serializer: add add_directive API (#125)
Support add_directive API in serializer to support use case such as adding SNP block for an existing IGVM file. Signed-off-by: Ming-Wei Shih <mishih@microsoft.com>
1 parent e6c3ff1 commit 031a4a5

2 files changed

Lines changed: 118 additions & 3 deletions

File tree

igvm/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3546,6 +3546,12 @@ impl IgvmFile {
35463546
self.directive_headers.as_slice()
35473547
}
35483548

3549+
/// Get a mutable reference to the directive headers in this file.
3550+
#[cfg(feature = "corim")]
3551+
pub(crate) fn directives_mut(&mut self) -> &mut Vec<IgvmDirectiveHeader> {
3552+
&mut self.directive_headers
3553+
}
3554+
35493555
/// Get the relocation regions and page table builder in this file for a
35503556
/// given compatibility mask. If relocation is not supported, None is
35513557
/// returned.

igvm/src/serializer.rs

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
4848
use crate::CorimTemplate;
4949
use crate::Error;
50+
use crate::IgvmDirectiveHeader;
5051
use crate::IgvmFile;
5152
use crate::IgvmInitializationHeader;
5253
use crate::IgvmPlatformHeader;
@@ -79,6 +80,7 @@ pub struct IgvmSerializer<'a> {
7980
file: &'a IgvmFile,
8081
measurements: Vec<IgvmPlatformMeasurement>,
8182
extra_init_headers: Vec<IgvmInitializationHeader>,
83+
extra_directive_headers: Vec<IgvmDirectiveHeader>,
8284
}
8385

8486
impl<'a> IgvmSerializer<'a> {
@@ -101,6 +103,7 @@ impl<'a> IgvmSerializer<'a> {
101103
file,
102104
measurements: Vec::new(),
103105
extra_init_headers: Vec::new(),
106+
extra_directive_headers: Vec::new(),
104107
};
105108

106109
// Eagerly compute the launch measurement for every supported
@@ -367,21 +370,40 @@ impl<'a> IgvmSerializer<'a> {
367370
.map_err(|e| Error::CorimGeneration(e.to_string()))
368371
}
369372

373+
/// Attach an extra directive header that will be appended when
374+
/// serializing.
375+
///
376+
/// This allows callers to stage workflows that need the already-cached
377+
/// launch measurement from [`new`](Self::new), then add a directive
378+
/// derived from that measurement (for example,
379+
/// [`IgvmDirectiveHeader::SnpIdBlock`]) before the final serialize.
380+
///
381+
/// Added directives are not included in the measurement cache computed
382+
/// during [`new`](Self::new).
383+
pub fn add_directive(&mut self, directive: IgvmDirectiveHeader) -> &IgvmDirectiveHeader {
384+
self.extra_directive_headers.push(directive);
385+
self.extra_directive_headers
386+
.last()
387+
.expect("just pushed a directive")
388+
}
389+
370390
/// Serialize the IGVM file to binary format, including any CoRIM
371391
/// documents that were added via [`add_corim`](Self::add_corim).
372392
///
373393
/// This produces the same binary format as [`IgvmFile::serialize`],
374-
/// but with additional initialization headers appended.
394+
/// but with additional initialization headers and directives appended.
375395
pub fn serialize(&self, output: &mut Vec<u8>) -> Result<(), Error> {
376-
if self.extra_init_headers.is_empty() {
396+
if self.extra_init_headers.is_empty() && self.extra_directive_headers.is_empty() {
377397
// Fast path: nothing added, delegate directly.
378398
self.file.serialize(output)
379399
} else {
380-
// Clone the file and append the extra init headers so that
400+
// Clone the file and append the extra headers so that
381401
// the original IgvmFile::serialize handles all the work.
382402
let mut file = self.file.clone();
383403
file.initializations_mut()
384404
.extend(self.extra_init_headers.iter().cloned());
405+
file.directives_mut()
406+
.extend(self.extra_directive_headers.iter().cloned());
385407
file.serialize(output)
386408
}
387409
}
@@ -400,6 +422,8 @@ mod tests {
400422
use igvm_defs::IgvmPageDataFlags;
401423
use igvm_defs::IgvmPageDataType;
402424
use igvm_defs::IgvmPlatformType;
425+
use igvm_defs::IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY;
426+
use igvm_defs::IGVM_VHS_SNP_ID_BLOCK_SIGNATURE;
403427
use igvm_defs::IGVM_VHS_SUPPORTED_PLATFORM;
404428
use igvm_defs::PAGE_SIZE_4K;
405429

@@ -476,6 +500,41 @@ mod tests {
476500
.unwrap()
477501
}
478502

503+
fn new_snp_id_block(mask: u32, ld: [u8; 48]) -> crate::IgvmDirectiveHeader {
504+
crate::IgvmDirectiveHeader::SnpIdBlock {
505+
compatibility_mask: mask,
506+
author_key_enabled: 0,
507+
reserved: [0; 3],
508+
ld,
509+
family_id: [0; 16],
510+
image_id: [0; 16],
511+
version: 1,
512+
guest_svn: 1,
513+
id_key_algorithm: 0,
514+
author_key_algorithm: 0,
515+
id_key_signature: Box::new(IGVM_VHS_SNP_ID_BLOCK_SIGNATURE {
516+
r_comp: [0; 72],
517+
s_comp: [0; 72],
518+
}),
519+
id_public_key: Box::new(IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY {
520+
curve: 0,
521+
reserved: 0,
522+
qx: [0; 72],
523+
qy: [0; 72],
524+
}),
525+
author_key_signature: Box::new(IGVM_VHS_SNP_ID_BLOCK_SIGNATURE {
526+
r_comp: [0; 72],
527+
s_comp: [0; 72],
528+
}),
529+
author_public_key: Box::new(IGVM_VHS_SNP_ID_BLOCK_PUBLIC_KEY {
530+
curve: 0,
531+
reserved: 0,
532+
qx: [0; 72],
533+
qy: [0; 72],
534+
}),
535+
}
536+
}
537+
479538
// -- Basic serializer tests --------------------------------------
480539

481540
#[test]
@@ -688,6 +747,56 @@ mod tests {
688747
assert_eq!(file.initializations().len(), init_count_before);
689748
}
690749

750+
#[test]
751+
fn add_directive_snp_id_block_appended_and_measurement_unchanged() {
752+
let file = make_snp_file();
753+
let mut serializer = IgvmSerializer::new(&file).unwrap();
754+
755+
// Capture the eagerly-computed SNP launch digest (stage 1).
756+
let digest_before = serializer
757+
.measurement_for(IgvmPlatformType::SEV_SNP)
758+
.expect("SNP measurement should be present")
759+
.digest
760+
.clone();
761+
let mut ld = [0u8; 48];
762+
ld.copy_from_slice(&digest_before);
763+
764+
// Stage 2: add a synthetic SNP ID block built from that digest.
765+
serializer.add_directive(new_snp_id_block(0x1, ld));
766+
767+
// Cached measurement must remain the one computed during `new`.
768+
let digest_after = serializer
769+
.measurement_for(IgvmPlatformType::SEV_SNP)
770+
.expect("SNP measurement should still be present")
771+
.digest
772+
.clone();
773+
assert_eq!(digest_before, digest_after);
774+
775+
// Final serialize should include the extra directive exactly once.
776+
let mut output = Vec::new();
777+
serializer.serialize(&mut output).unwrap();
778+
779+
let deserialized = IgvmFile::new_from_binary(&output, None).unwrap();
780+
let id_blocks = deserialized
781+
.directives()
782+
.iter()
783+
.filter(|h| matches!(h, crate::IgvmDirectiveHeader::SnpIdBlock { .. }))
784+
.count();
785+
assert_eq!(id_blocks, 1);
786+
}
787+
788+
#[test]
789+
fn file_not_mutated_after_add_directive() {
790+
let file = make_snp_file();
791+
let directive_count_before = file.directives().len();
792+
793+
let mut serializer = IgvmSerializer::new(&file).unwrap();
794+
serializer.add_directive(new_snp_id_block(0x1, [0x11; 48]));
795+
796+
// The original file should not have been mutated.
797+
assert_eq!(file.directives().len(), directive_count_before);
798+
}
799+
691800
// -- Two-stage builder tests -------------------------------------
692801

693802
#[test]

0 commit comments

Comments
 (0)