blob: 56602378858ca3bc36145afae50f5e94c47d3b66 [file] [log] [blame]
Lorenz Brunee17d832022-10-18 12:02:45 +00001// Package gpt implements reading and writing GUID Partition Tables as specified
2// in the UEFI Specification. It only implements up to 128 partitions per table
3// (same as most other implementations) as more would require a dynamic table
4// size, significantly complicating the code for little gain.
5package gpt
6
7import (
8 "bytes"
9 "encoding/binary"
10 "errors"
11 "fmt"
12 "hash/crc32"
13 "io"
14 "sort"
15 "strings"
16 "unicode/utf16"
17
18 "github.com/google/uuid"
19)
20
21var gptSignature = [8]byte{'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'}
22var gptRevision uint32 = 0x00010000 // First 2 bytes major, second 2 bytes minor
23
24// See UEFI Specification 2.9 Table 5-5
25type header struct {
26 Signature [8]byte
27 Revision uint32
28 HeaderSize uint32
29 HeaderCRC32 uint32
30 _ [4]byte
31
32 HeaderBlock uint64
33 AlternateHeaderBlock uint64
34 FirstUsableBlock uint64
35 LastUsableBlock uint64
36
37 ID [16]byte
38
39 PartitionEntriesStartBlock uint64
40 PartitionEntryCount uint32
41 PartitionEntrySize uint32
42 PartitionEntriesCRC32 uint32
43}
44
45// See UEFI Specification 2.9 Table 5-6
46type partition struct {
47 Type [16]byte
48 ID [16]byte
49 FirstBlock uint64
50 LastBlock uint64
51 Attributes uint64
52 Name [36]uint16
53}
54
55var (
56 PartitionTypeEFISystem = uuid.MustParse("C12A7328-F81F-11D2-BA4B-00A0C93EC93B")
57)
58
59var zeroUUID = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
60
61// Attribute is a bitfield of attributes set on a partition. Bits 0 to 47 are
62// reserved for UEFI specification use and all current assignments are in the
63// following const block. Bits 48 to 64 are available for per-Type use by
64// the organization controlling the partition Type.
65type Attribute uint64
66
67const (
68 // AttrRequiredPartition indicates that this partition is required for the
69 // platform to function. Mostly used by vendors to mark things like recovery
70 // partitions.
71 AttrRequiredPartition = 1 << 0
72 // AttrNoBlockIOProto indicates that EFI firmware must not provide an EFI
73 // block device (EFI_BLOCK_IO_PROTOCOL) for this partition.
74 AttrNoBlockIOProto = 1 << 1
75 // AttrLegacyBIOSBootable indicates to special-purpose software outside of
76 // UEFI that this partition can be booted using a traditional PC BIOS.
77 // Don't use this unless you know that you need it specifically.
78 AttrLegacyBIOSBootable = 1 << 2
79)
80
81// PerTypeAttrs returns the top 24 bits which are reserved for custom per-Type
82// attributes. The top 8 bits of the returned uint32 are always 0.
83func (a Attribute) PerTypeAttrs() uint32 {
84 return uint32(a >> 48)
85}
86
87// SetPerTypeAttrs sets the top 24 bits which are reserved for custom per-Type
88// attributes. It does not touch the lower attributes which are specified by the
89// UEFI specification. The top 8 bits of v are silently discarded.
90func (a *Attribute) SetPerTypeAttrs(v uint32) {
91 *a &= 0x000000FF_FFFFFFFF
92 *a |= Attribute(v) << 48
93}
94
95type Partition struct {
96 // Name of the partition, will be truncated if it expands to more than 36
97 // UTF-16 code points. Not all systems can display non-BMP code points.
98 Name string
99 // Type is the type of Table partition, can either be one of the predefined
100 // constants by the UEFI specification or a custom type identifier.
101 // Note that the all-zero UUID denotes an empty partition slot, so this
102 // MUST be set to something, otherwise it is not treated as a partition.
103 Type uuid.UUID
104 // ID is a unique identifier for this specific partition. It should be
105 // changed when cloning the partition.
106 ID uuid.UUID
107 // The first logical block of the partition (inclusive)
108 FirstBlock uint64
109 // The last logical block of the partition (inclusive)
110 LastBlock uint64
111 // Bitset of attributes of this partition.
112 Attributes Attribute
113}
114
115// SizeBlocks returns the size of the partition in blocks
116func (p *Partition) SizeBlocks() uint64 {
117 return 1 + p.LastBlock - p.FirstBlock
118}
119
120// IsUnused checks if the partition is unused, i.e. it is nil or its type is
121// the null UUID.
122func (p *Partition) IsUnused() bool {
123 if p == nil {
124 return true
125 }
126 return p.Type == zeroUUID
127}
128
129type Table struct {
130 // ID is the unique identifier of this specific disk / GPT.
131 // If this is left uninitialized/all-zeroes a new random ID is automatically
132 // generated when writing.
133 ID uuid.UUID
134
135 // Data put at the start of the very first block. Gets loaded and executed
136 // by a legacy BIOS bootloader. This can be used to make GPT-partitioned
137 // disks bootable by legacy systems or display a nice error message.
138 // Maximum length is 440 bytes, if that is exceeded Write returns an error.
139 // Should be left empty if the device is not bootable and/or compatibility
140 // with BIOS booting is not required. Only useful on x86 systems.
141 BootCode []byte
142
143 // BlockSize contains the logical block size of the block device. It must
144 // be a power of two equal to or larger than 512.
145 BlockSize int64
146 // BlockCount contains the number of logical blocks on the block device.
147 // BlockCount times BlockSize is the size of the block device in bytes.
148 BlockCount int64
149
150 // Partitions contains the list of partitions in this table. This is
151 // artificially limited to 128 partitions.
152 Partitions []*Partition
153}
154type addOptions struct {
155 preferEnd bool
156 keepEmptyEntries bool
157 alignment int64
158}
159
160// AddOption is a bitset controlling various
161type AddOption func(*addOptions)
162
163// WithPreferEnd tries to put the partition as close to the end as possible
164// instead of as close to the start.
165func WithPreferEnd() AddOption {
166 return func(options *addOptions) {
167 options.preferEnd = true
168 }
169}
170
171// WithKeepEmptyEntries does not fill up empty entries which are followed by
172// filled ones. It always appends the partition after the last used entry.
173// Without this flag, the partition is placed in the first empty entry.
174func WithKeepEmptyEntries() AddOption {
175 return func(options *addOptions) {
176 options.keepEmptyEntries = true
177 }
178}
179
180// WithAlignment allows aligning the partition start block to a non-default
181// value. By default, these are aligned to 1MiB.
182// Only use this flag if you are certain you need it, it can cause quite severe
183// performance degradation under certain conditions.
184func WithAlignment(alignmenet int64) AddOption {
185 return func(options *addOptions) {
186 options.alignment = alignmenet
187 }
188}
189
190// AddPartition takes a pointer to a partition and adds it, placing it into
191// the first (or last using WithPreferEnd) continuous free space which fits it.
192// It writes the placement information (FirstBlock, LastBlock) back to p.
193// By default, AddPartition aligns FirstBlock to 1MiB boundaries, but this can
194// be overridden using WithAlignment.
195func (g *Table) AddPartition(p *Partition, size int64, options ...AddOption) error {
196 if g.BlockSize < 512 {
197 return errors.New("block size is smaller than 512 bytes, this is unsupported")
198 }
199 var opts addOptions
200 // Align to 1MiB or the block size, whichever is bigger
201 opts.alignment = 1 * 1024 * 1024
202 if g.BlockSize > opts.alignment {
203 opts.alignment = g.BlockSize
204 }
205 for _, o := range options {
206 o(&opts)
207 }
208 if opts.alignment%g.BlockSize != 0 {
209 return fmt.Errorf("requested alignment (%d bytes) is not an integer multiple of the block size (%d), unable to align", opts.alignment, g.BlockSize)
210 }
211 // Number of blocks the partition should occupy, rounded up.
212 blocks := (size + g.BlockSize - 1) / g.BlockSize
213 fs, _, err := g.GetFreeSpaces()
214 if err != nil {
215 return fmt.Errorf("unable to determine free space: %v", err)
216 }
217 if opts.preferEnd {
218 // Reverse fs slice to start iteration at the end
219 for i, j := 0, len(fs)-1; i < j; i, j = i+1, j-1 {
220 fs[i], fs[j] = fs[j], fs[i]
221 }
222 }
223 var maxFreeBlocks int64
224 for _, freeInt := range fs {
225 start := freeInt[0]
226 end := freeInt[1]
227 freeBlocks := end - start
228 // Align start properly
229 paddingBlocks := (-start) % (opts.alignment / g.BlockSize)
230 freeBlocks -= paddingBlocks
231 start += paddingBlocks
232 if maxFreeBlocks < freeBlocks {
233 maxFreeBlocks = freeBlocks
234 }
235 if freeBlocks >= blocks {
236 if !opts.preferEnd {
237 p.FirstBlock = uint64(start)
238 p.LastBlock = uint64(start + blocks)
239 } else {
240 // Realign FirstBlock. This will always succeed as
241 // there is enough space to align to the start.
242 moveLeft := (end - blocks - 1) % (opts.alignment / g.BlockSize)
243 p.FirstBlock = uint64(end - (blocks + 1 + moveLeft))
244 p.LastBlock = uint64(end - (1 + moveLeft))
245 }
246 newPartPos := -1
247 if !opts.keepEmptyEntries {
248 for i, part := range g.Partitions {
249 if part.IsUnused() {
250 newPartPos = i
251 break
252 }
253 }
254 }
255 if newPartPos == -1 {
256 g.Partitions = append(g.Partitions, p)
257 } else {
258 g.Partitions[newPartPos] = p
259 }
260 return nil
261 }
262 }
263
264 return fmt.Errorf("no space for partition of %d blocks, largest continuous free space after alignment is %d blocks", blocks, maxFreeBlocks)
265}
266
267// FirstUsableBlock returns the first usable (i.e. a partition can start there)
268// block.
269func (g *Table) FirstUsableBlock() int64 {
270 partitionEntryBlocks := (16384 + g.BlockSize - 1) / g.BlockSize
271 return 2 + partitionEntryBlocks
272}
273
274// LastUsableBlock returns the last usable (i.e. a partition can end there)
275// block. This block is inclusive.
276func (g *Table) LastUsableBlock() int64 {
277 partitionEntryBlocks := (16384 + g.BlockSize - 1) / g.BlockSize
278 return g.BlockCount - (2 + partitionEntryBlocks)
279}
280
281// GetFreeSpaces returns a slice of tuples, each containing a half-closed
282// interval of logical blocks not occupied by the GPT itself or any partition.
283// The returned intervals are always in ascending order as well as
284// non-overlapping. It also returns if it detected any overlaps between
285// partitions or partitions and the GPT. It returns an error if and only if any
286// partition has its FirstBlock before the LastBlock or exceeds the amount of
287// blocks on the block device.
288//
289// Note that the most common use cases for this function are covered by
290// AddPartition, you're encouraged to use it instead.
291func (g *Table) GetFreeSpaces() ([][2]int64, bool, error) {
292 // This implements an efficient algorithm for finding free intervals given
293 // a set of potentially overlapping occupying intervals. It uses O(n*log n)
294 // time for n being the amount of intervals, i.e. partitions. It uses O(n)
295 // additional memory. This makes it de facto infinitely scalable in the
296 // context of partition tables as the size of the block device is not part
297 // of its cyclomatic complexity and O(n*log n) is tiny for even very big
298 // partition tables.
299
300 // startBlocks contains the start blocks (inclusive) of all occupied
301 // intervals.
302 var startBlocks []int64
303 // endBlocks contains the end blocks (exclusive!) of all occupied intervals.
304 // The interval at index i is given by [startBlock[i], endBlock[i]).
305 var endBlocks []int64
306
307 // Reserve the primary GPT interval including the protective MBR.
308 startBlocks = append(startBlocks, 0)
309 endBlocks = append(endBlocks, g.FirstUsableBlock())
310
311 // Reserve the alternate GPT interval (needs +1 for exclusive interval)
312 startBlocks = append(startBlocks, g.LastUsableBlock()+1)
313 endBlocks = append(endBlocks, g.BlockCount)
314
315 for i, part := range g.Partitions {
316 if part.IsUnused() {
317 continue
318 }
319 // Bail if partition does not contain a valid interval. These are open
320 // intervals, thus part.FirstBlock == part.LastBlock denotes a valid
321 // partition with a size of one block.
322 if part.FirstBlock > part.LastBlock {
323 return nil, false, fmt.Errorf("partition %d has a LastBlock smaller than its FirstBlock, its interval is [%d, %d]", i, part.FirstBlock, part.LastBlock)
324 }
325 if part.FirstBlock >= uint64(g.BlockCount) || part.LastBlock >= uint64(g.BlockCount) {
326 return nil, false, fmt.Errorf("partition %d exceeds the block count of the block device", i)
327 }
328 startBlocks = append(startBlocks, int64(part.FirstBlock))
329 // Algorithm needs open-closed intervals, thus add +1 to the end.
330 endBlocks = append(endBlocks, int64(part.LastBlock)+1)
331 }
332 // Sort both sets of blocks independently in ascending order. Note that it
333 // is now no longer possible to extract the original intervals. Integers
334 // have no identity thus it doesn't matter if the sort is stable or not.
335 sort.Slice(startBlocks, func(i, j int) bool { return startBlocks[i] < startBlocks[j] })
336 sort.Slice(endBlocks, func(i, j int) bool { return endBlocks[i] < endBlocks[j] })
337
338 var freeSpaces [][2]int64
339
340 // currentIntervals contains the number of intervals which contain the
341 // position currently being iterated over. If currentIntervals is ever
342 // bigger than 1, there is overlap within the given intervals.
343 currentIntervals := 0
344 var hasOverlap bool
345
346 // Iterate for as long as there are interval boundaries to be processed.
347 for len(startBlocks) != 0 || len(endBlocks) != 0 {
348 // Short-circuit boundary processing. If an interval ends at x and the
349 // next one starts at x (this is using half-open intervals), it would
350 // otherwise perform useless processing as well as create an empty free
351 // interval which would then need to be filtered back out.
352 if len(startBlocks) != 0 && len(endBlocks) != 0 && startBlocks[0] == endBlocks[0] {
353 startBlocks = startBlocks[1:]
354 endBlocks = endBlocks[1:]
355 continue
356 }
357 // Pick the lowest boundary from either startBlocks or endBlocks,
358 // preferring endBlocks if they are equal. Don't try to pick from empty
359 // slices.
360 if (len(startBlocks) != 0 && len(endBlocks) != 0 && startBlocks[0] < endBlocks[0]) || len(endBlocks) == 0 {
361 // If currentIntervals == 0 a free space region ends here.
362 // Since this algorithm creates the free space interval at the end
363 // of an occupied interval, for the first interval there is no free
364 // space entry. But in this case it's fine to just ignore it as the
365 // first interval always starts at 0 because of the GPT.
366 if currentIntervals == 0 && len(freeSpaces) != 0 {
367 freeSpaces[len(freeSpaces)-1][1] = startBlocks[0]
368 }
369 // This is the start of an interval, increase the number of active
370 // intervals.
371 currentIntervals++
372 hasOverlap = hasOverlap || currentIntervals > 1
373 // Drop processed startBlock from slice.
374 startBlocks = startBlocks[1:]
375 } else {
376 // This is the end of an interval, decrease the number of active
377 // intervals.
378 currentIntervals--
379 // If currentIntervals == 0 a free space region starts here.
380 // Same as with the startBlocks, ignore a potential free block after
381 // the final range as the GPT occupies the last blocks anyway.
382 if currentIntervals == 0 && len(startBlocks) != 0 {
383 freeSpaces = append(freeSpaces, [2]int64{endBlocks[0], 0})
384 }
385 endBlocks = endBlocks[1:]
386 }
387 }
388 return freeSpaces, hasOverlap, nil
389}
390
391// Overhead returns the number of blocks the GPT partitioning itself consumes,
392// i.e. aren't usable for user data.
393func Overhead(blockSize int64) int64 {
394 // 3 blocks + 2x 16384 bytes (partition entry space)
395 partitionEntryBlocks := (16384 + blockSize - 1) / blockSize
396 return 3 + (2 * partitionEntryBlocks)
397}
398
399// Write writes a list of GPT partitions with a protective MBR to the given
400// WriteSeeker. It must have a defined end, i.e. w.Seek(-x, io.SeekEnd) must
401// seek to x bytes before the end of the disk. If gpt.ID or any of the
402// partition IDs are the all-zero UUID, a new random one is generated and
403// written back. If the output is supposed to be reproducible, generate the
404// UUIDs beforehand.
405func Write(w io.WriteSeeker, gpt *Table) error {
406 if gpt.BlockSize < 512 {
407 return errors.New("block size is smaller than 512 bytes, this is unsupported")
408 }
409 // Layout looks as follows:
410 // Block 0: Protective MBR
411 // Block 1: GPT Header
412 // Block 2-(16384 bytes): GPT partition entries
413 // Block (16384 bytes)-n: GPT partition entries alternate copy
414 // Block n: GPT Header alternate copy
415 if len(gpt.Partitions) > 128 {
416 return errors.New("Bigger-than default GPTs (>128 partitions) are unimplemented")
417 }
418
419 partitionEntryBlocks := (16384 + gpt.BlockSize - 1) / gpt.BlockSize
420 if gpt.BlockCount < 3+(2*partitionEntryBlocks) {
421 return errors.New("not enough blocks to write GPT")
422 }
423
424 if gpt.ID == zeroUUID {
425 gpt.ID = uuid.New()
426 }
427
428 partSize := binary.Size(partition{})
429 slotCount := 128
430
431 var partitionEntriesData bytes.Buffer
432 for i := 0; i < slotCount; i++ {
433 if len(gpt.Partitions) <= i || gpt.Partitions[i] == nil {
434 partitionEntriesData.Write(make([]byte, partSize))
435 continue
436 }
437 p := gpt.Partitions[i]
438 if p.ID == zeroUUID {
439 p.ID = uuid.New()
440 }
441 rawP := partition{
442 Type: mangleUUID(p.Type),
443 ID: mangleUUID(p.ID),
444 FirstBlock: p.FirstBlock,
445 LastBlock: p.LastBlock,
446 Attributes: uint64(p.Attributes),
447 }
448 nameUTF16 := utf16.Encode([]rune(p.Name))
449 // copy will automatically truncate if target is too short
450 copy(rawP.Name[:], nameUTF16)
451 binary.Write(&partitionEntriesData, binary.LittleEndian, rawP)
452 }
453
454 hdr := header{
455 Signature: gptSignature,
456 Revision: gptRevision,
457 HeaderSize: uint32(binary.Size(&header{})),
458 ID: mangleUUID(gpt.ID),
459
460 PartitionEntryCount: uint32(slotCount),
461 PartitionEntrySize: uint32(partSize),
462
463 FirstUsableBlock: uint64(2 + partitionEntryBlocks),
464 LastUsableBlock: uint64(gpt.BlockCount - (2 + partitionEntryBlocks)),
465 }
466 hdr.PartitionEntriesCRC32 = crc32.ChecksumIEEE(partitionEntriesData.Bytes())
467
468 hdrChecksum := crc32.NewIEEE()
469
470 // Write alternate header first, as otherwise resizes are unsafe. If the
471 // alternate is currently not at the end of the block device, it cannot
472 // be found. Thus if the write operation is aborted abnormally, the
473 // primary GPT is corrupted and the alternate cannot be found because it
474 // is not at its canonical location. Rewriting the alternate first avoids
475 // this problem.
476
477 // Alternate header
478 if _, err := w.Seek((gpt.LastUsableBlock()+1)*gpt.BlockSize, io.SeekStart); err != nil {
479 return fmt.Errorf("failed to seek to end of block device: %w", err)
480 }
481 hdr.HeaderBlock = uint64(gpt.BlockCount - 1)
482 hdr.AlternateHeaderBlock = 1
483 hdr.PartitionEntriesStartBlock = uint64(gpt.BlockCount - (1 + partitionEntryBlocks))
484
485 hdrChecksum.Reset()
486 hdr.HeaderCRC32 = 0
487 binary.Write(hdrChecksum, binary.LittleEndian, &hdr)
488 hdr.HeaderCRC32 = hdrChecksum.Sum32()
489
490 if _, err := w.Write(partitionEntriesData.Bytes()); err != nil {
491 return fmt.Errorf("failed to write alternate partition entries: %w", err)
492 }
493 if _, err := w.Seek((gpt.BlockCount-1)*gpt.BlockSize, io.SeekStart); err != nil {
494 return fmt.Errorf("failed to seek to end of block device: %w", err)
495 }
496
497 if err := binary.Write(w, binary.LittleEndian, &hdr); err != nil {
498 return fmt.Errorf("failed to write alternate header: %w", err)
499 }
500 if _, err := w.Write(make([]byte, gpt.BlockSize-int64(binary.Size(hdr)))); err != nil {
501 return fmt.Errorf("failed to write padding: %v", err)
502 }
503
504 // Primary header
505 if _, err := w.Seek(0, io.SeekStart); err != nil {
506 return fmt.Errorf("failed to seek to end of block device: %w", err)
507 }
508 hdr.HeaderBlock = 1
509 hdr.AlternateHeaderBlock = uint64(gpt.BlockCount - 1)
510 hdr.PartitionEntriesStartBlock = 2
511
512 hdrChecksum.Reset()
513 hdr.HeaderCRC32 = 0
514 binary.Write(hdrChecksum, binary.LittleEndian, &hdr)
515 hdr.HeaderCRC32 = hdrChecksum.Sum32()
516
517 if err := makeProtectiveMBR(w, gpt.BlockCount, gpt.BootCode); err != nil {
518 return fmt.Errorf("failed to write first block: %w", err)
519 }
520
521 if err := binary.Write(w, binary.LittleEndian, &hdr); err != nil {
522 return fmt.Errorf("failed to write primary header: %w", err)
523 }
524 if _, err := w.Write(make([]byte, gpt.BlockSize-int64(binary.Size(hdr)))); err != nil {
525 return fmt.Errorf("failed to write padding: %v", err)
526 }
527 if _, err := w.Write(partitionEntriesData.Bytes()); err != nil {
528 return fmt.Errorf("failed to write primary partition entries: %w", err)
529 }
530 return nil
531}
532
533// Read reads a Table from a block device given its block size and count.
534func Read(r io.ReadSeeker, blockSize int64, blockCount int64) (*Table, error) {
535 if Overhead(blockSize) > blockCount {
536 return nil, errors.New("disk cannot contain a GPT as the block count is too small to store one")
537 }
538 if _, err := r.Seek(0, io.SeekStart); err != nil {
539 return nil, fmt.Errorf("failed to seek to block 0: %w", err)
540 }
541 zeroBlock := make([]byte, blockSize)
542 if _, err := io.ReadFull(r, zeroBlock); err != nil {
543 return nil, fmt.Errorf("failed to read first two blocks: %w", err)
544 }
545
546 var m mbr
547 if err := binary.Read(bytes.NewReader(zeroBlock[:512]), binary.LittleEndian, &m); err != nil {
548 panic(err) // Read is from memory and with enough data
549 }
550 // The UEFI standard says that the only acceptable MBR for a GPT-partitioned
551 // device is a pure protective MBR with one partition of type 0xEE covering
552 // the entire disk. But reality is sadly not so simple. People have come up
553 // with hacks like Hybrid MBR which is basically a way to expose partitions
554 // as both GPT partitions and MBR partitions. There are also GPTs without
555 // any MBR at all.
556 // Following the standard strictly when reading means that this library
557 // would fail to read valid GPT disks where such schemes are employed.
558 // On the other hand just looking at the GPT signature is also dangerous
559 // as not all tools clear the second block where the GPT resides when
560 // writing an MBR, which results in reading a wrong/obsolete GPT.
561 // As a pragmatic solution this library treats any disk as GPT-formatted if
562 // the first block does not contain an MBR signature or at least one MBR
563 // partition has type 0xEE (GPT). It does however not care in which slot
564 // this partition is or if it begins at the start of the disk.
565 //
566 // Note that the block signatures for MBR and FAT are shared. This is a
567 // historical artifact from DOS. It is not reliably possible to
568 // differentiate the two as either has boot code where the other has meta-
569 // data and both lack any checksums. Because the MBR partition table is at
570 // the very end of the FAT bootcode section the following code always
571 // assumes that it is dealing with an MBR. This is both more likely and
572 // the 0xEE marker is rarer and thus more specific than FATs 0x00, 0x80 and
573 // 0x02.
574 var bootCode []byte
575 hasDOSBootSig := m.Signature == mbrSignature
576 if hasDOSBootSig {
577 var isGPT bool
578 for _, p := range m.PartitionRecords {
579 if p.Type == 0xEE {
580 isGPT = true
581 }
582 }
583 // Note that there is a small but non-zero chance that isGPT is true
584 // for a raw FAT filesystem if the bootcode contains a "valid" MBR.
585 // The next error message mentions that possibility.
586 if !isGPT {
587 return nil, errors.New("block device contains an MBR table without a GPT marker or a raw FAT filesystem")
588 }
589 // Trim right zeroes away as they are padded back when writing. This
590 // makes BootCode empty when it is all-zeros, making it easier to work
591 // with while still round-tripping correctly.
592 bootCode = bytes.TrimRight(m.BootCode[:], "\x00")
593 }
594 // Read the primary GPT. If it is damaged and/or broken, read the alternate.
595 primaryGPT, err := readSingleGPT(r, blockSize, blockCount, 1)
596 if err != nil {
597 alternateGPT, err2 := readSingleGPT(r, blockSize, blockCount, blockCount-1)
598 if err2 != nil {
599 return nil, fmt.Errorf("failed to read both GPTs: primary GPT (%v), secondary GPT (%v)", err, err2)
600 }
601 alternateGPT.BootCode = bootCode
602 return alternateGPT, nil
603 }
604 primaryGPT.BootCode = bootCode
605 return primaryGPT, nil
606}
607
608func readSingleGPT(r io.ReadSeeker, blockSize int64, blockCount int64, headerBlockPos int64) (*Table, error) {
609 if _, err := r.Seek(blockSize*headerBlockPos, io.SeekStart); err != nil {
610 return nil, fmt.Errorf("failed to seek to block %d: %w", headerBlockPos, err)
611 }
612 hdrBlock := make([]byte, blockSize)
613 if _, err := io.ReadFull(r, hdrBlock); err != nil {
614 return nil, fmt.Errorf("failed to read GPT header block: %w", err)
615 }
616 hdrBlockReader := bytes.NewReader(hdrBlock)
617 var hdr header
618 if err := binary.Read(hdrBlockReader, binary.LittleEndian, &hdr); err != nil {
619 panic(err) // Read from memory with enough bytes, should not fail
620 }
621 if hdr.Signature != gptSignature {
622 return nil, errors.New("no GPT signature found")
623 }
624 if hdr.HeaderSize < uint32(binary.Size(hdr)) {
625 return nil, fmt.Errorf("GPT header size is too small, likely corrupted")
626 }
627 if int64(hdr.HeaderSize) > blockSize {
628 return nil, fmt.Errorf("GPT header size is bigger than block size, likely corrupted")
629 }
630 // Use reserved bytes to hash, but do not expose them to the user.
631 // If someone has a need to process them, they should extend this library
632 // with whatever an updated UEFI specification contains.
633 // It has been considered to store these in the user-exposed GPT struct to
634 // be able to round-trip them cleanly, but there is significant complexity
635 // and risk involved in doing so.
636 reservedBytes := hdrBlock[binary.Size(hdr):hdr.HeaderSize]
637 hdrExpectedCRC := hdr.HeaderCRC32
638 hdr.HeaderCRC32 = 0
639 hdrCRC := crc32.NewIEEE()
640 binary.Write(hdrCRC, binary.LittleEndian, &hdr)
641 hdrCRC.Write(reservedBytes)
642 if hdrCRC.Sum32() != hdrExpectedCRC {
643 return nil, fmt.Errorf("GPT header checksum mismatch, probably corrupted")
644 }
645 if hdr.HeaderBlock != uint64(headerBlockPos) {
646 return nil, errors.New("GPT header indicates wrong block")
647 }
648 if hdr.PartitionEntrySize < uint32(binary.Size(partition{})) {
649 return nil, errors.New("partition entry size too small")
650 }
651 if hdr.PartitionEntriesStartBlock > uint64(blockCount) {
652 return nil, errors.New("partition entry start block is out of range")
653 }
654 // Sanity-check total size of the partition entry area. Otherwise, this is a
655 // trivial DoS as it could cause allocation of gigabytes of memory.
656 // 4MiB is equivalent to around 45k partitions at the current size.
657 // I know of no operating system which would handle even a fraction of this.
658 if uint64(hdr.PartitionEntryCount)*uint64(hdr.PartitionEntrySize) > 4*1024*1024 {
659 return nil, errors.New("partition entry area bigger than 4MiB, refusing to read")
660 }
661 partitionEntryData := make([]byte, hdr.PartitionEntrySize*hdr.PartitionEntryCount)
662 if _, err := r.Seek(blockSize*int64(hdr.PartitionEntriesStartBlock), io.SeekStart); err != nil {
663 return nil, fmt.Errorf("failed to seek to partition entry start block: %w", err)
664 }
665 if _, err := io.ReadFull(r, partitionEntryData); err != nil {
666 return nil, fmt.Errorf("failed to read partition entries: %w", err)
667 }
668 if crc32.ChecksumIEEE(partitionEntryData) != hdr.PartitionEntriesCRC32 {
669 return nil, errors.New("GPT partition entry table checksum mismatch")
670 }
671 var g Table
672 g.ID = unmangleUUID(hdr.ID)
673 g.BlockSize = blockSize
674 g.BlockCount = blockCount
675 for i := uint32(0); i < hdr.PartitionEntryCount; i++ {
676 entryReader := bytes.NewReader(partitionEntryData[i*hdr.PartitionEntrySize : (i+1)*hdr.PartitionEntrySize])
677 var part partition
678 if err := binary.Read(entryReader, binary.LittleEndian, &part); err != nil {
679 panic(err) // Should not happen
680 }
681 // If the partition type is the all-zero UUID, this slot counts as
682 // unused.
683 if part.Type == zeroUUID {
684 g.Partitions = append(g.Partitions, nil)
685 continue
686 }
687 g.Partitions = append(g.Partitions, &Partition{
688 ID: unmangleUUID(part.ID),
689 Type: unmangleUUID(part.Type),
690 Name: strings.TrimRight(string(utf16.Decode(part.Name[:])), "\x00"),
691 FirstBlock: part.FirstBlock,
692 LastBlock: part.LastBlock,
693 Attributes: Attribute(part.Attributes),
694 })
695 }
696 // Remove long list of nils at the end as it's inconvenient to work with
697 // (append doesn't work, debug prints are very long) and it round-trips
698 // correctly even without it as it gets zero-padded when writing anyway.
699 var maxValidPartition int
700 for i, p := range g.Partitions {
701 if !p.IsUnused() {
702 maxValidPartition = i
703 }
704 }
705 g.Partitions = g.Partitions[:maxValidPartition+1]
706 return &g, nil
707}
708
709var mixedEndianTranspose = []int{3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15}
710
711// mangleUUID encodes a normal UUID into a "mixed-endian" UUID. This just means
712// shuffling the bytes around in a specific way. Thank Microsoft for this
713// idiosyncrasy.
714func mangleUUID(u uuid.UUID) (o [16]byte) {
715 for dest, from := range mixedEndianTranspose {
716 o[dest] = u[from]
717 }
718 return
719}
720
721// unmangleUUID does the reverse transformation of mangleUUID
722func unmangleUUID(i [16]byte) (o uuid.UUID) {
723 for from, dest := range mixedEndianTranspose {
724 o[dest] = i[from]
725 }
726 return
727}