diff --git a/td-shim-tools/src/bin/td-payload-reference-calculator/README.md b/td-shim-tools/src/bin/td-payload-reference-calculator/README.md index a59fd6ce..c18042fe 100644 --- a/td-shim-tools/src/bin/td-payload-reference-calculator/README.md +++ b/td-shim-tools/src/bin/td-payload-reference-calculator/README.md @@ -13,7 +13,7 @@ wget https://github.com/Xynnn007/td-payload-reference-provider/raw/main/tests/bz Test with the example bzImage ``` -cargo run -- kernel -k bzImage -s 0x10000000 +cargo run -p td-shim-tools --bin td-payload-reference-calculator -- kernel -k bzImage -s 0x10000000 ``` The `kernel-size` parameter here means `KERNEL_SIZE` defined in guest firmware, s.t. [TD-SHIM](https://github.com/confidential-containers/td-shim) @@ -25,11 +25,23 @@ Will get the result which is from https://github.com/confidential-containers/attestation-service/pull/33/files#diff-1a4e5ad4c3b043c019c00bc3b3072fd6e1e5b03a5ce8c498e1c0acaf697d9d3fR265 +When using [TDVF](https://github.com/tianocore/edk2-staging/tree/2024-tdvf-ww01.4/OvmfPkg) + [QEMU](https://github.com/qemu/qemu) Kernel Direct Boot, the kernel eventlog digest will be pre-processed by QEMU Kernel Direct Boot logic. Which is from https://github.com/confidential-containers/td-shim/issues/633 + +Test with the example bzImage +``` +cargo run -p td-shim-tools --bin td-payload-reference-calculator -- kernel -k bzImage -q +``` + +Will get the result +``` +a5e921ae5bde7ab989216da059057741688eae9114b854ce60733824f93ade8a848f19c719f3fdd5c4f0d7178164a5e2 +``` + ### Kernel Parameter Test ``` -cargo run -- param -p "root=/dev/vda1 console=hvc0 rw" -s 0x1000 +cargo run -p td-shim-tools --bin td-payload-reference-calculator -- param -p "root=/dev/vda1 console=hvc0 rw" -s 0x1000 ``` Will get the result diff --git a/td-shim-tools/src/bin/td-payload-reference-calculator/main.rs b/td-shim-tools/src/bin/td-payload-reference-calculator/main.rs index 939cec9d..55236919 100644 --- a/td-shim-tools/src/bin/td-payload-reference-calculator/main.rs +++ b/td-shim-tools/src/bin/td-payload-reference-calculator/main.rs @@ -14,6 +14,18 @@ use std::{convert::TryFrom, path::Path}; pub const KERNEL_SIZE: &str = "0x2000000"; pub const KERNEL_PARAM_SIZE: &str = "0x1000"; +// Refer to https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#signature-image-only, +// file offset specified at offset 0x3c, +// size of PE signature is 4: "PE\0\0" +const IMAGE_PE_OFFSET: usize = 0x003c; +const PE_SIGNATURE_SIZE: u32 = 4; +const IMGAE_BEGIN_CHECKSUM_ADDR: usize = 0x0000; +const IMGAE_BEGIN_CHECKSUM_SIZE: usize = 0x00da; +const IMGAE_CERT_TABLE_ADDR: usize = 0x00de; +const IMGAE_CERT_TABLE_SIZE: usize = 0x004c; +const IMGAE_HEADERS_ADDR: usize = 0x0132; +const IMGAE_HEADERS_SIZE: usize = 0x00ce; + fn kernel(path: &str, size: &str) -> Result { let path = Path::new(path).to_path_buf(); let siz = parse::(size)?; @@ -31,6 +43,106 @@ fn param(param: &str, size: &str) -> Result { padding_digest(param, siz) } +fn qemu(path: &str, size: &str) -> Result { + let path = Path::new(path).to_path_buf(); + let siz = parse::(size)?; + let file_size = std::fs::metadata(&path)?.len(); + if file_size > siz { + bail!("File size should be less than `kernel-size`"); + } + let buf = std::fs::read(path)?; + qemu_patch(buf) +} + +fn qemu_patch(mut buf: Vec) -> Result { + // refer to https://github.com/qemu/qemu/blob/f48c205fb42be48e2e47b7e1cd9a2802e5ca17b0/hw/i386/x86.c#L999 + // patching type_of_loader @0x210 + buf[0x210] = 0xb0; + + // refer to https://github.com/qemu/qemu/blob/f48c205fb42be48e2e47b7e1cd9a2802e5ca17b0/hw/i386/x86.c#L1003 + // patching loadflags @0x211 + buf[0x211] = 0x81; + + // refer to https://github.com/qemu/qemu/blob/9c74490bff6c8886a922008d0c9ce6cae70dd17e/hw/i386/x86.c#L1004 + // patching heap_end_ptr @0x224 cmdline_addr - real_addr - 0x200 = 0xfe00 + buf[0x224] = 0x00; + buf[0x225] = 0xfe; + + // refer to https://github.com/qemu/qemu/blob/9c74490bff6c8886a922008d0c9ce6cae70dd17e/hw/i386/x86.c#L962 + // patching cmd_line_ptr @0x228 cmdline_addr = 0x20000 + buf[0x228] = 0x00; + buf[0x229] = 0x00; + buf[0x22A] = 0x02; + buf[0x22B] = 0x00; + + let mut hasher = sha2::Sha384::new(); + let (number_of_region_entry, regions_base, regions_size) = get_image_regions(&buf); + + for index in 0..number_of_region_entry { + hasher.update(&buf[regions_base[index]..regions_base[index] + regions_size[index]]); + } + + let res = hasher.finalize(); + Ok(hex::encode(res)) +} + +fn get_image_regions(buf: &[u8]) -> (usize, Vec, Vec) { + // These 3 regions are known. + let mut number_of_region_entry = 3; + let mut regions_base = vec![ + IMGAE_BEGIN_CHECKSUM_ADDR, + IMGAE_CERT_TABLE_ADDR, + IMGAE_HEADERS_ADDR, + ]; + let mut regions_size = vec![ + IMGAE_BEGIN_CHECKSUM_SIZE, + IMGAE_CERT_TABLE_SIZE, + IMGAE_HEADERS_SIZE, + ]; + + // Refer to https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image, + // After the signature of an image file is COFF File Header, size is 20 bytes. + // the NumberOfSections' offset is 2 and size is 2 bytes. + let size_of_coff_file_header: u32 = 20; + + let coff_file_header_offset = ((buf[IMAGE_PE_OFFSET + 3] as u32) << 24) + | ((buf[IMAGE_PE_OFFSET + 2] as u32) << 16) + | ((buf[IMAGE_PE_OFFSET + 1] as u32) << 8) + | (buf[IMAGE_PE_OFFSET] as u32) + PE_SIGNATURE_SIZE; + + let number_of_pecoff_entry = ((buf[coff_file_header_offset as usize + 3] as u16) << 8) + | buf[coff_file_header_offset as usize + 2] as u16; + number_of_region_entry += number_of_pecoff_entry as usize; + + // the SizeOfOptionalHeader's offset is 16 and size is 2 bytes + let size_of_optional_header = ((buf[coff_file_header_offset as usize + 17] as u16) << 8) + | buf[coff_file_header_offset as usize + 16] as u16; + + // Refer to https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers + // Size Of each Section is 40 bytes + // SizeOfRawData Offset: 16 Size:4 + // PointerToRawData Offset: 20 Size:4 + let mut p = (coff_file_header_offset + + size_of_coff_file_header + + size_of_optional_header as u32) as usize; + for _i in 0..number_of_pecoff_entry { + p += 16; + let size = ((buf[p + 3] as u32) << 24) + | ((buf[p + 2] as u32) << 16) + | ((buf[p + 1] as u32) << 8) + | buf[p] as u32; + p += 4; + let base = ((buf[p + 3] as u32) << 24) + | ((buf[p + 2] as u32) << 16) + | ((buf[p + 1] as u32) << 8) + | buf[p] as u32; + regions_base.push(base as usize); + regions_size.push(size as usize); + p += 20; + } + (number_of_region_entry, regions_base, regions_size) +} + fn padding_digest(mut buf: Vec, len: usize) -> Result { let diff = len - buf.len(); @@ -56,7 +168,8 @@ fn main() { .required(false) .default_value(KERNEL_SIZE) .action(ArgAction::Set), - ), + ) + .arg(arg!(-q --"qemu" "QEMU Kernel Direct Boot patch string").required(false)), ) .subcommand( command!("param") @@ -78,7 +191,12 @@ fn main() { Some(("kernel", args)) => { let path = args.get_one::("kernel").unwrap(); let siz = args.get_one::("size").unwrap(); - kernel(path, siz) + // let qflag = args.get_one::("qemu").unwrap(); + if args.get_flag("qemu") { + qemu(path, siz) + } else { + kernel(path, siz) + } } Some(("param", args)) => { let parameter = args.get_one::("parameter").unwrap();