Alioth Code Coverage

tdx.rs71.01%

1// Copyright 2026 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cmp::min;
16use std::io::Write;
17
18use snafu::ResultExt;
19use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
20
21use crate::firmware::ovmf::x86_64::{GUID_SIZE, parse_data};
22use crate::firmware::uefi::{
23 HOB_HANDOFF_TABLE_VERSION, HobGenericHeader, HobHandoffInfoTable, HobResourceDesc,
24 HobResourceType, HobType, ResourceAttr,
25};
26use crate::firmware::{Result, error};
27use crate::mem::{MemRegionEntry, MemRegionType};
28use crate::{bitflags, consts};
29
30pub const GUID_TDX_METADATA_OFFSET: [u8; GUID_SIZE] = [
31 0x35, 0x65, 0x7a, 0xe4, 0x4a, 0x98, 0x98, 0x47, 0x86, 0x5e, 0x46, 0x85, 0xa7, 0xbf, 0x8e, 0xc2,
32];
33pub const TDVF_SIGNATURE: u32 = u32::from_le_bytes(*b"TDVF");
34pub const TDVF_VERSION: u32 = 1;
35
36#[repr(C)]
37#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)]
38pub struct TdvfMetadata {
39 pub signature: u32,
40 pub length: u32,
41 pub version: u32,
42 pub number_of_entries: u32,
43}
44
45consts! {
46 #[derive(Default, KnownLayout, Immutable, FromBytes, IntoBytes)]
47 pub struct TdvfSectionType(u32) {
48 BFV = 0;
49 CFV = 1;
50 TD_HOB = 2;
51 TEMP_MEM = 3;
52 }
53}
54
55bitflags! {
56 #[derive(Default, KnownLayout, Immutable, FromBytes, IntoBytes)]
57 pub struct TdvfSectionAttr(u32) {
58 MR_EXTEND = 1 << 0;
59 PAGE_AUG = 1 << 1;
60 }
61}
62
63#[repr(C)]
64#[derive(Debug, Clone, Default, KnownLayout, Immutable, FromBytes, IntoBytes)]
65pub struct TdvfSectionEntry {
66 pub data_offset: u32,
67 pub data_size: u32,
68 pub address: u64,
69 pub size: u64,
70 pub r#type: TdvfSectionType,
71 pub attributes: TdvfSectionAttr,
72}
73
74pub fn parse_entries(data: &[u8]) -> Result<&[TdvfSectionEntry]> {
75 let Some(offset_r) = parse_data(data, &GUID_TDX_METADATA_OFFSET) else {
76 return error::MissingMetadata {
77 name: "TdvfMetadata",
78 }
79 .fail();
80 };
81 let Ok(offset_r) = u32::read_from_bytes(offset_r) else {
82 return error::InvalidLayout.fail();
83 };
84 let offset = data.len() - offset_r as usize;
85 let Ok((metadata, remain)) = TdvfMetadata::ref_from_prefix(&data[offset..]) else {
86 return error::InvalidLayout.fail();
87 };
88 if metadata.signature != TDVF_SIGNATURE {
89 return error::MissingTdvfSignature {
90 got: metadata.signature,
91 }
92 .fail();
93 }
94 if metadata.version != TDVF_VERSION {
95 return error::MissingTdvfVersion {
96 got: metadata.version,
97 }
98 .fail();
99 }
100 let Ok((entries, _)) = <[TdvfSectionEntry]>::ref_from_prefix_with_elems(
101 remain,
102 metadata.number_of_entries as usize,
103 ) else {
104 return error::InvalidLayout.fail();
105 };
106 Ok(entries)
107}
108
109fn create_hob_mem_resources(3x
110 entries: &[(u64, MemRegionEntry)],3x
111 accepted: &[(u64, u64)],3x
112 mut op: impl FnMut(HobResourceDesc) -> Result<()>,3x
113) -> Result<()> {3x
114 let tmpl = HobResourceDesc {3x
115 hdr: HobGenericHeader {3x
116 r#type: HobType::RESOURCE_DESCRIPTOR,3x
117 length: size_of::<HobResourceDesc>() as u16,3x
118 reserved: 0,3x
119 },3x
120 owner: [0; 16],3x
121 attr: ResourceAttr::PRESENT | ResourceAttr::INIT | ResourceAttr::TESTED,3x
122 ..Default::default()3x
123 };3x
124 let mut iter_e = entries.iter();3x
125 let mut iter_a = accepted.iter();3x
126 let mut section = iter_a.next().copied();3x
127 let mut entry = iter_e.next().copied();3x
128 loop {
129 match (&mut entry, &mut section) {24x
130 (None, None) => break,3x
131 (None, Some((start, size))) => {
132 log::error!(
133 "Section [{start:x}, {:x}) is not covered by system memory",
134 *start + *size
135 );
136 return error::UncoveredTdvfSection.fail();
137 }
138 (Some((_, e)), _) if e.type_ != MemRegionType::Ram => {21x
139 entry = iter_e.next().copied();9x
140 continue;9x
141 }
142 (Some((s, e)), None) => {3x
143 op(HobResourceDesc {3x
144 r#type: HobResourceType::MEMORY_UNACCEPTED,3x
145 address: *s,3x
146 len: e.size,3x
147 ..tmpl.clone()3x
148 })?;3x
149 entry = iter_e.next().copied();3x
150 }
151 (Some((s, e)), Some((start, size))) => {9x
152 if let Some(len) = min(*s, *start + *size).checked_sub(*start)9x
153 && len > 06x
154 {
155 *size = len;
156 entry = None;
157 // Jump to branch (Some, None)
158 continue;
159 }9x
160 if let Some(len) = min(*s + e.size, *start).checked_sub(*s)9x
161 && len > 09x
162 {
163 op(HobResourceDesc {3x
164 r#type: HobResourceType::MEMORY_UNACCEPTED,3x
165 address: *s,3x
166 len,3x
167 ..tmpl.clone()3x
168 })?;3x
169 *s += len;3x
170 e.size -= len;3x
171 }6x
172 if let Some(len) = min(*s + e.size, *start + *size).checked_sub(*start)9x
173 && len > 09x
174 {
175 op(HobResourceDesc {9x
176 r#type: HobResourceType::SYSTEM_MEMORY,9x
177 address: *start,9x
178 len,9x
179 ..tmpl.clone()9x
180 })?;9x
181 *start += len;9x
182 *size -= len;9x
183 *s += len;9x
184 e.size -= len9x
185 };
186 if *size == 0 {9x
187 section = iter_a.next().copied();9x
188 };9x
189 if e.size == 0 {9x
190 entry = iter_e.next().copied();
191 }9x
192 }
193 }
194 }
195 Ok(())3x
196}3x
197
198pub fn create_hob(3x
199 buffer: &mut [u8],3x
200 hob_phys: u64,3x
201 entries: &mut [(u64, MemRegionEntry)],3x
202 accepted: &mut [(u64, u64)],3x
203) -> Result<()> {3x
204 entries.sort_by_key(|(s, _)| *s);3x
205 accepted.sort();3x
206
207 let Ok((table, mut dst)) = HobHandoffInfoTable::mut_from_prefix(buffer) else {3x
208 return error::InvalidLayout.fail();
209 };
210
211 let mut desc_size = 0;3x
212 create_hob_mem_resources(entries, accepted, |d| {15x
213 desc_size += size_of_val(&d);15x
214 dst.write_all(d.as_bytes()).context(error::WriteHob)15x
215 })?;15x
216
217 let end = HobGenericHeader {3x
218 r#type: HobType::END_OF_HOB_LIST,3x
219 length: size_of::<HobGenericHeader>() as u16,3x
220 reserved: 0,3x
221 };3x
222 dst.write_all(end.as_bytes()).context(error::WriteHob)?;3x
223
224 *table = HobHandoffInfoTable {3x
225 hdr: HobGenericHeader {3x
226 r#type: HobType::HANDOFF,3x
227 length: size_of::<HobHandoffInfoTable>() as u16,3x
228 reserved: 0,3x
229 },3x
230 version: HOB_HANDOFF_TABLE_VERSION,3x
231 end_of_hob_list: hob_phys + (size_of_val(table) + desc_size + size_of_val(&end)) as u64,3x
232 ..Default::default()3x
233 };3x
234
235 Ok(())3x
236}3x
237
238#[cfg(test)]
239#[path = "tdx_test.rs"]
240mod tests;
241