Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for caculating kernel eventlog digist when using OVMF+QEMU #655

Merged
merged 1 commit into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions td-shim-tools/src/bin/td-payload-reference-calculator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ The test bzImage can be fetched via
```bash
wget https://github.com/Xynnn007/td-payload-reference-provider/raw/main/tests/bzImage
```
Note: The protocol version of bzImage should be 2.06+.

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)
Expand All @@ -25,11 +26,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
Expand Down
136 changes: 134 additions & 2 deletions td-shim-tools/src/bin/td-payload-reference-calculator/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ 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;

// Refer to https://www.kernel.org/doc/html/latest/arch/x86/boot.html#details-of-header-fields
// Protocol version addr: 0x206, size: 2
const IMAGE_PROTOCOL_ADDR: usize = 0x0206;

fn kernel(path: &str, size: &str) -> Result<String> {
let path = Path::new(path).to_path_buf();
let siz = parse::<u64>(size)?;
Expand All @@ -22,6 +38,11 @@ fn kernel(path: &str, size: &str) -> Result<String> {
bail!("File size should be less than `kernel-size`");
}
let buf = std::fs::read(path)?;
let protocol = ((buf[IMAGE_PROTOCOL_ADDR as usize + 1] as u16) << 8)
| buf[IMAGE_PROTOCOL_ADDR as usize] as u16;
if protocol < 0x206 {
bail!("Protocol version should be 2.06+");
}
padding_digest(buf, siz as usize)
}

Expand All @@ -31,6 +52,111 @@ fn param(param: &str, size: &str) -> Result<String> {
padding_digest(param, siz)
}

fn qemu(path: &str, size: &str) -> Result<String> {
let path = Path::new(path).to_path_buf();
let siz = parse::<u64>(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)?;
let protocol = ((buf[IMAGE_PROTOCOL_ADDR as usize + 1] as u16) << 8)
| buf[IMAGE_PROTOCOL_ADDR as usize] as u16;
if protocol < 0x206 {
bail!("Protocol version should be 2.06+");
}
qemu_patch(buf)
}

fn qemu_patch(mut buf: Vec<u8>) -> Result<String> {
// 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;
Comment on lines +87 to +89
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a stl_p() in https://github.com/qemu/qemu/blob/f48c205fb42be48e2e47b7e1cd9a2802e5ca17b0/hw/i386/x86.c#L962C9-L962C14 which means the dst length is 4 bytes (uint32_t) due to underlying definition https://github.com/qemu/qemu/blob/f48c205fb42be48e2e47b7e1cd9a2802e5ca17b0/include/qemu/bswap.h#L325

Thus we might need to set one more byte here? s.t.

buf[0x22B] = 0x00;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reasonable, I will set one more byte here.

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<usize>, Vec<usize>) {
// 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<u8>, len: usize) -> Result<String> {
let diff = len - buf.len();

Expand All @@ -56,7 +182,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")
Expand All @@ -78,7 +205,12 @@ fn main() {
Some(("kernel", args)) => {
let path = args.get_one::<String>("kernel").unwrap();
let siz = args.get_one::<String>("size").unwrap();
kernel(path, siz)
// let qflag = args.get_one::<String>("qemu").unwrap();
if args.get_flag("qemu") {
qemu(path, siz)
} else {
kernel(path, siz)
}
}
Some(("param", args)) => {
let parameter = args.get_one::<String>("parameter").unwrap();
Expand Down
Loading