1use anyhow::{Error, anyhow, ensure};
6use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
7
8const MAX_PARTITION_ENTRIES: u32 = 128;
9
10pub const GPT_SIGNATURE: [u8; 8] = [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54];
11pub const GPT_REVISION: u32 = 0x10000;
12pub const GPT_HEADER_SIZE: usize = 92;
13
14#[derive(Clone, Debug, Eq, PartialEq, Immutable, IntoBytes, KnownLayout, FromBytes)]
16#[repr(C)]
17pub struct Header {
18 pub signature: [u8; 8],
20 pub revision: u32,
22 pub header_size: u32,
24 pub crc32: u32,
26 pub reserved: u32,
28 pub current_lba: u64,
30 pub backup_lba: u64,
32 pub first_usable: u64,
34 pub last_usable: u64,
36 pub disk_guid: [u8; 16],
38 pub part_start: u64,
40 pub num_parts: u32,
42 pub part_size: u32,
44 pub crc32_parts: u32,
46 zerocopy_padding: u32,
49}
50
51impl Header {
52 pub fn new(block_count: u64, block_size: u32, num_parts: u32) -> Result<Self, Error> {
53 ensure!(block_size > 0 && block_size.is_power_of_two(), "Invalid block size");
54 let bs = block_size as u64;
55
56 let part_size = std::mem::size_of::<PartitionTableEntry>();
57 let partition_table_len = num_parts as u64 * part_size as u64;
58 let partition_table_blocks = partition_table_len.checked_next_multiple_of(bs).unwrap() / bs;
59
60 ensure!(block_count > 1 + 2 * (1 + partition_table_blocks), "Too few blocks");
63
64 let mut this = Self {
65 signature: GPT_SIGNATURE,
66 revision: GPT_REVISION,
67 header_size: GPT_HEADER_SIZE as u32,
68 crc32: 0,
69 reserved: 0,
70 current_lba: 1,
71 backup_lba: block_count - 1,
72 first_usable: 2 + partition_table_blocks,
73 last_usable: block_count - (2 + partition_table_blocks),
74 disk_guid: uuid::Uuid::new_v4().into_bytes(),
75 part_start: 2,
76 num_parts,
77 part_size: part_size as u32,
78 crc32_parts: 0,
79 zerocopy_padding: 0,
80 };
81 this.update_checksum();
82 Ok(this)
83 }
84
85 pub fn compute_checksum(&self) -> u32 {
87 let mut header_copy = self.clone();
88 header_copy.crc32 = 0;
89 crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC)
90 .checksum(&header_copy.as_bytes()[..GPT_HEADER_SIZE])
91 }
92
93 fn update_checksum(&mut self) {
94 self.crc32 = 0;
95 let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC)
96 .checksum(&self.as_bytes()[..GPT_HEADER_SIZE]);
97 self.crc32 = crc;
98 }
99
100 pub fn ensure_integrity(&self, block_count: u64, block_size: u64) -> Result<(), Error> {
102 ensure!(self.signature == GPT_SIGNATURE, "Bad signature {:x?}", self.signature);
103 ensure!(self.revision == GPT_REVISION, "Bad revision {:x}", self.revision);
104 ensure!(
105 self.header_size as usize == GPT_HEADER_SIZE,
106 "Bad header size {}",
107 self.header_size
108 );
109
110 ensure!(self.crc32 == self.compute_checksum(), "Invalid header checksum");
113
114 ensure!(self.num_parts <= MAX_PARTITION_ENTRIES, "Invalid num_parts {}", self.num_parts);
115 ensure!(
116 self.part_size as usize == std::mem::size_of::<PartitionTableEntry>(),
117 "Invalid part_size {}",
118 self.part_size
119 );
120 let partition_table_blocks = (self
121 .num_parts
122 .checked_mul(self.part_size)
123 .and_then(|v| v.checked_next_multiple_of(block_size as u32))
124 .ok_or_else(|| {
125 anyhow!(
126 "Partition table size overflow \
127 (num_parts: {}, part_size: {}, block_size: {block_size})",
128 self.num_parts,
129 self.part_size
130 )
131 })? as u64)
132 / block_size;
133 ensure!(
134 partition_table_blocks < block_count,
135 "Invalid partition table size: \
136 {partition_table_blocks} blocks >= {block_count} block_count"
137 );
138
139 ensure!(
143 self.current_lba == 1 || self.current_lba == block_count - 1,
144 "Invalid current_lba {}",
145 self.current_lba
146 );
147 if self.current_lba == 1 {
148 ensure!(
153 self.backup_lba < block_count,
154 "backup_lba out of bounds {} (block_count {block_count})",
155 self.backup_lba,
156 );
157 } else {
158 ensure!(self.backup_lba == 1, "Invalid backup_lba {}", self.backup_lba);
159 }
160 let (first_lba, second_lba) = if self.current_lba == 1 {
161 (self.current_lba, self.backup_lba)
162 } else {
163 (self.backup_lba, self.current_lba)
164 };
165
166 let min_first_usable = first_lba
167 .checked_add(1)
168 .and_then(|v| v.checked_add(partition_table_blocks))
169 .ok_or_else(|| {
170 anyhow!(
171 "Overflow calculating min_first_usable \
172 (first_lba: {first_lba}, partition_table_blocks: {partition_table_blocks})"
173 )
174 })?;
175 ensure!(
176 self.first_usable >= min_first_usable,
177 "Invalid first_usable {} (minimum: {})",
178 self.first_usable,
179 min_first_usable
180 );
181 let last_usable_end =
182 self.last_usable.checked_add(partition_table_blocks).ok_or_else(|| {
183 anyhow!(
184 "Overflow calculating last_usable_end \
185 (last_usable: {}, partition_table_blocks: {partition_table_blocks})",
186 self.last_usable
187 )
188 })?;
189 ensure!(
190 self.first_usable <= self.last_usable && last_usable_end < second_lba,
191 "Invalid last_usable {} (first_usable: {}, last_usable_end: {}, second_lba: {})",
192 self.last_usable,
193 self.first_usable,
194 last_usable_end,
195 second_lba
196 );
197
198 if first_lba == self.current_lba {
199 ensure!(self.part_start == first_lba + 1, "Invalid part_start {}", self.part_start);
200 } else {
201 let expected_part_start = self.last_usable.checked_add(1).ok_or_else(|| {
202 anyhow!(
203 "Overflow calculating expected part_start (last_usable: {})",
204 self.last_usable
205 )
206 })?;
207 ensure!(
208 self.part_start == expected_part_start,
209 "Invalid part_start {} (expected: {})",
210 self.part_start,
211 expected_part_start
212 );
213 }
214
215 Ok(())
216 }
217}
218
219#[derive(Clone, Debug, Eq, PartialEq, Immutable, IntoBytes, KnownLayout, FromBytes)]
220#[repr(C)]
221pub struct PartitionTableEntry {
222 pub type_guid: [u8; 16],
223 pub instance_guid: [u8; 16],
224 pub first_lba: u64,
225 pub last_lba: u64,
226 pub flags: u64,
227 pub name: [u16; 36],
228}
229
230impl PartitionTableEntry {
231 pub fn is_empty(&self) -> bool {
232 self.as_bytes().iter().all(|b| *b == 0)
233 }
234
235 pub fn empty() -> Self {
236 Self {
237 type_guid: [0u8; 16],
238 instance_guid: [0u8; 16],
239 first_lba: 0,
240 last_lba: 0,
241 flags: 0,
242 name: [0u16; 36],
243 }
244 }
245
246 pub fn ensure_integrity(&self, first_usable: u64, last_usable: u64) -> Result<(), Error> {
247 ensure!(self.type_guid != [0u8; 16], "Empty type GUID");
248 ensure!(self.instance_guid != [0u8; 16], "Empty instance GUID");
249 ensure!(
250 self.first_lba >= first_usable,
251 "Invalid first LBA {} (first_usable: {})",
252 self.first_lba,
253 first_usable
254 );
255 ensure!(
256 self.last_lba <= last_usable && self.last_lba >= self.first_lba,
257 "Invalid last LBA {} (first_usable: {}, last_usable: {}, first_lba: {})",
258 self.last_lba,
259 first_usable,
260 last_usable,
261 self.first_lba
262 );
263 Ok(())
264 }
265}
266
267#[derive(Eq, thiserror::Error, Clone, Debug, PartialEq)]
268pub enum FormatError {
269 #[error("Invalid arguments")]
270 InvalidArguments,
271 #[error("No space")]
272 NoSpace,
273}
274
275pub fn serialize_partition_table(
279 header: &mut Header,
280 block_size: usize,
281 num_blocks: u64,
282 entries: &[PartitionTableEntry],
283) -> Result<Vec<u8>, FormatError> {
284 let crc_algo = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
285 let mut digest = crc_algo.digest();
286 let partition_table_len = header.part_size as usize * entries.len();
287 let partition_table_len = partition_table_len
288 .checked_next_multiple_of(block_size)
289 .ok_or(FormatError::InvalidArguments)?;
290 let partition_table_blocks = (partition_table_len / block_size) as u64;
291 let mut partition_table = vec![0u8; partition_table_len];
292 let mut partition_table_view = &mut partition_table[..];
293 let first_usable = partition_table_blocks + 2;
295 let last_usable = num_blocks.saturating_sub(partition_table_blocks + 2);
298 if first_usable > last_usable {
299 return Err(FormatError::NoSpace);
300 }
301 let last_usable_end = last_usable.checked_add(1).ok_or(FormatError::InvalidArguments)?;
302 let mut used_ranges = vec![0..first_usable, last_usable_end..num_blocks];
303 let part_size = header.part_size as usize;
304 for entry in entries {
305 let part_raw = entry.as_bytes();
306 assert!(part_raw.len() == part_size);
307 if !entry.is_empty() {
308 entry
309 .ensure_integrity(first_usable, last_usable)
310 .map_err(|_| FormatError::InvalidArguments)?;
311 let end = entry.last_lba.checked_add(1).ok_or(FormatError::InvalidArguments)?;
312 used_ranges.push(entry.first_lba..end);
313 partition_table_view[..part_raw.len()].copy_from_slice(part_raw);
314 }
315 digest.update(part_raw);
316 partition_table_view = &mut partition_table_view[part_size..];
317 }
318 used_ranges.sort_by_key(|range| range.start);
319 for [a, b] in used_ranges.array_windows() {
320 if a.end > b.start {
321 return Err(FormatError::InvalidArguments);
322 }
323 }
324 header.first_usable = first_usable;
325 header.last_usable = last_usable;
326 header.num_parts = entries.len() as u32;
327 header.crc32_parts = digest.finalize();
328 header.crc32 = header.compute_checksum();
329 Ok(partition_table)
330}
331
332#[cfg(test)]
333mod tests {
334 use super::{
335 FormatError, GPT_HEADER_SIZE, Header, PartitionTableEntry, serialize_partition_table,
336 };
337
338 #[fuchsia::test]
339 fn header_crc() {
340 let nblocks = 8;
341 let partition_table_nblocks = 1;
342 let mut header = Header {
343 signature: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54],
344 revision: 0x10000,
345 header_size: GPT_HEADER_SIZE as u32,
346 crc32: 0,
347 reserved: 0,
348 current_lba: 1,
349 backup_lba: nblocks - 1,
350 first_usable: 2 + partition_table_nblocks,
351 last_usable: nblocks - (2 + partition_table_nblocks),
352 disk_guid: [0u8; 16],
353 part_start: 2,
354 num_parts: 1,
355 part_size: 128,
356 crc32_parts: 0,
357 zerocopy_padding: 0,
358 };
359 header.crc32 = header.compute_checksum();
360
361 header.ensure_integrity(nblocks, 512).expect("Header should be valid");
362
363 header.num_parts = 2;
365
366 header.ensure_integrity(nblocks, 512).expect_err("Header should be invalid");
367 }
368
369 #[fuchsia::test]
370 fn test_backup_lba_validation() {
371 let nblocks = 10;
372 let partition_table_nblocks = 1;
373 let mut header = Header {
374 signature: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54],
375 revision: 0x10000,
376 header_size: GPT_HEADER_SIZE as u32,
377 crc32: 0,
378 reserved: 0,
379 current_lba: 1,
380 backup_lba: nblocks - 1,
381 first_usable: 2 + partition_table_nblocks,
382 last_usable: nblocks - (2 + partition_table_nblocks),
383 disk_guid: [0u8; 16],
384 part_start: 2,
385 num_parts: 1,
386 part_size: 128,
387 crc32_parts: 0,
388 zerocopy_padding: 0,
389 };
390
391 header.crc32 = header.compute_checksum();
394 header.ensure_integrity(nblocks, 512).expect("Header should be valid");
395
396 header.backup_lba = nblocks - 2;
398 header.last_usable = 6;
399 header.crc32 = header.compute_checksum();
400 header
401 .ensure_integrity(nblocks, 512)
402 .expect("Header should be valid with relaxed backup_lba");
403
404 header.backup_lba = nblocks;
406 header.last_usable = nblocks - (2 + partition_table_nblocks);
407 header.crc32 = header.compute_checksum();
408 header.ensure_integrity(nblocks, 512).expect_err("backup_lba >= nblocks should be invalid");
409
410 header.current_lba = nblocks - 1;
412 header.backup_lba = 1;
413 header.part_start = header.last_usable + 1;
414 header.crc32 = header.compute_checksum();
415 header
416 .ensure_integrity(nblocks, 512)
417 .expect("Backup header should be valid with backup_lba == 1");
418
419 header.backup_lba = 2;
420 header.crc32 = header.compute_checksum();
421 header
422 .ensure_integrity(nblocks, 512)
423 .expect_err("Backup header should be invalid with backup_lba != 1");
424 }
425
426 #[fuchsia::test]
427 fn test_header_ensure_integrity_last_usable_overflow() {
428 let nblocks = 10;
429 let partition_table_nblocks = 1;
430 let mut header = Header {
431 signature: [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54],
432 revision: 0x10000,
433 header_size: GPT_HEADER_SIZE as u32,
434 crc32: 0,
435 reserved: 0,
436 current_lba: 1,
437 backup_lba: nblocks - 1,
438 first_usable: 2 + partition_table_nblocks,
439 last_usable: u64::MAX,
440 disk_guid: [0u8; 16],
441 part_start: 2,
442 num_parts: 1,
443 part_size: 128,
444 crc32_parts: 0,
445 zerocopy_padding: 0,
446 };
447 header.crc32 = header.compute_checksum();
448 header
449 .ensure_integrity(nblocks, 512)
450 .expect_err("last_usable = u64::MAX should fail ensure_integrity");
451 }
452
453 #[fuchsia::test]
454 fn test_serialize_partition_table_overflow_entry() {
455 let block_count = 1024;
456 let block_size = 512;
457 let mut header = Header::new(block_count, block_size, 128).unwrap();
458 let mut entries = vec![PartitionTableEntry::empty(); 128];
459 entries[0] = PartitionTableEntry {
460 type_guid: [1; 16],
461 instance_guid: [1; 16],
462 first_lba: u64::MAX,
463 last_lba: u64::MAX,
464 flags: 0,
465 name: [0; 36],
466 };
467 let result =
468 serialize_partition_table(&mut header, block_size as usize, block_count, &entries[..]);
469 assert_eq!(result, Err(FormatError::InvalidArguments));
470 }
471}