1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright (c) 2019, Joyent, Inc.
14 */
15
16 /*
17 * The on-disk elements here are all little-endian, and this code doesn't make
18 * any attempt to adjust for running on a big-endian system.
19 *
20 * We also currently assume a 512-byte sized logical block.
21 */
22
23 #include <sys/types.h>
24 #include <sys/crc32.h>
25 #include <sys/debug.h>
26 #include <sys/sysmacros.h>
27 #include <sys/dktp/fdisk.h>
28 #include <sys/efi_partition.h>
29
30 #include <assert.h>
31 #include <ctype.h>
32 #include <uuid/uuid.h>
33
34 #include <mdb/mdb_modapi.h>
35 #include <mdb/mdb_debug.h>
36
37 #include "installboot.h"
38
39 #ifdef _BIG_ENDIAN
40 #error needs porting for big-endian system
41 #endif
42
43 /* See usr/src/grub/grub-0.97/stage1/stage1.h */
44 #define GRUB_VERSION_OFF (0x3e)
45 #define GRUB_COMPAT_VERSION_MAJOR 3
46 #define GRUB_COMPAT_VERSION_MINOR 2
47 #define GRUB_VERSION (2 << 8 | 3) /* 3.2 */
48
49 #define LOADER_VERSION (1)
50 #define LOADER_JOYENT_VERSION (2)
51
52 typedef enum {
53 MBR_TYPE_UNKNOWN,
54 MBR_TYPE_GRUB1,
55 MBR_TYPE_LOADER,
56 MBR_TYPE_LOADER_JOYENT,
57 } mbr_type_t;
58
59 static void
60 print_fdisk_part(struct ipart *ip, size_t nr)
61 {
62 char typestr[128];
63 char begchs[128];
64 char endchs[128];
65 char *c = NULL;
66
67 if (ip->systid == UNUSED) {
68 mdb_printf("%-4llu %s:%#lx\n", nr, "UNUSED", ip->systid);
69 return;
70 }
71
72 switch (ip->systid) {
73 case DOSOS12: c = "DOSOS12"; break;
74 case PCIXOS: c = "PCIXOS"; break;
75 case DOSOS16: c = "DOSOS16"; break;
76 case EXTDOS: c = "EXTDOS"; break;
77 case DOSHUGE: c = "DOSHUGE"; break;
78 case FDISK_IFS: c = "FDISK_IFS"; break;
79 case FDISK_AIXBOOT: c = "FDISK_AIXBOOT"; break;
80 case FDISK_AIXDATA: c = "FDISK_AIXDATA"; break;
81 case FDISK_OS2BOOT: c = "FDISK_OS2BOOT"; break;
82 case FDISK_WINDOWS: c = "FDISK_WINDOWS"; break;
83 case FDISK_EXT_WIN: c = "FDISK_EXT_WIN"; break;
84 case FDISK_FAT95: c = "FDISK_FAT95"; break;
85 case FDISK_EXTLBA: c = "FDISK_EXTLBA"; break;
86 case DIAGPART: c = "DIAGPART"; break;
87 case FDISK_LINUX: c = "FDISK_LINUX"; break;
88 case FDISK_LINUXDSWAP: c = "FDISK_LINUXDSWAP"; break;
89 case FDISK_LINUXDNAT: c = "FDISK_LINUXDNAT"; break;
90 case FDISK_CPM: c = "FDISK_CPM"; break;
91 case DOSDATA: c = "DOSDATA"; break;
92 case OTHEROS: c = "OTHEROS"; break;
93 case UNIXOS: c = "UNIXOS"; break;
94 case FDISK_NOVELL2: c = "FDISK_NOVELL2"; break;
95 case FDISK_NOVELL3: c = "FDISK_NOVELL3"; break;
96 case FDISK_QNX4: c = "FDISK_QNX4"; break;
97 case FDISK_QNX42: c = "FDISK_QNX42"; break;
98 case FDISK_QNX43: c = "FDISK_QNX43"; break;
99 case SUNIXOS: c = "SUNIXOS"; break;
100 case FDISK_LINUXNAT: c = "FDISK_LINUXNAT"; break;
101 case FDISK_NTFSVOL1: c = "FDISK_NTFSVOL1"; break;
102 case FDISK_NTFSVOL2: c = "FDISK_NTFSVOL2"; break;
103 case FDISK_BSD: c = "FDISK_BSD"; break;
104 case FDISK_NEXTSTEP: c = "FDISK_NEXTSTEP"; break;
105 case FDISK_BSDIFS: c = "FDISK_BSDIFS"; break;
106 case FDISK_BSDISWAP: c = "FDISK_BSDISWAP"; break;
107 case X86BOOT: c = "X86BOOT"; break;
108 case SUNIXOS2: c = "SUNIXOS2"; break;
109 case EFI_PMBR: c = "EFI_PMBR"; break;
110 case EFI_FS: c = "EFI_FS"; break;
111 default: c = NULL; break;
112 }
113
114 if (c != NULL) {
115 mdb_snprintf(typestr, sizeof (typestr), "%s:%#lx",
116 c, ip->systid);
117 } else {
118 mdb_snprintf(typestr, sizeof (typestr), "%#lx", ip->systid);
119 }
120
121 mdb_snprintf(begchs, sizeof (begchs), "%hu/%hu/%hu",
122 (uint16_t)ip->begcyl | (uint16_t)(ip->begsect & ~0x3f) << 2,
123 (uint16_t)ip->beghead, (uint16_t)ip->begsect & 0x3f);
124 mdb_snprintf(endchs, sizeof (endchs), "%hu/%hu/%hu",
125 (uint16_t)ip->endcyl | (uint16_t)(ip->endsect & ~0x3f) << 2,
126 (uint16_t)ip->endhead, (uint16_t)ip->endsect & 0x3f);
127
128 mdb_printf("%-4llu %-21s %#-7x %-11s %-11s %-10u %-9u\n",
129 nr, typestr, ip->bootid, begchs, endchs, ip->relsect, ip->numsect);
130 }
131
132 static int
133 cmd_mbr(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv __unused)
134 {
135 struct mboot mbr;
136 mbr_type_t type = MBR_TYPE_UNKNOWN;
137
138 CTASSERT(sizeof (mbr) == SECTOR_SIZE);
139
140 if (argc != 0)
141 return (DCMD_USAGE);
142
143 if (!(flags & DCMD_ADDRSPEC))
144 addr = 0;
145
146 if (mdb_vread(&mbr, sizeof (mbr), addr) == -1) {
147 mdb_warn("failed to read MBR");
148 return (DCMD_ERR);
149 }
150
151 if (*((uint16_t *)&mbr.bootinst[GRUB_VERSION_OFF]) == GRUB_VERSION) {
152 type = MBR_TYPE_GRUB1;
153 } else if (mbr.bootinst[STAGE1_MBR_VERSION] == LOADER_VERSION) {
154 type = MBR_TYPE_LOADER;
155 } else if (mbr.bootinst[STAGE1_MBR_VERSION] == LOADER_JOYENT_VERSION) {
156 type = MBR_TYPE_LOADER_JOYENT;
157 }
158
159 switch (type) {
160 case MBR_TYPE_UNKNOWN:
161 mdb_printf("Format: unknown\n");
162 break;
163 case MBR_TYPE_GRUB1:
164 mdb_printf("Format: grub1\n");
165 break;
166 case MBR_TYPE_LOADER:
167 mdb_printf("Format: loader (illumos)\n");
168 break;
169 case MBR_TYPE_LOADER_JOYENT:
170 mdb_printf("Format: loader (joyent)\n");
171 break;
172 }
173
174 mdb_printf("Signature: 0x%hx (%s)\n", mbr.signature,
175 mbr.signature == MBB_MAGIC ? "valid" : "invalid");
176
177 mdb_printf("UniqueMBRDiskSignature: %#lx\n",
178 *(uint32_t *)&mbr.bootinst[STAGE1_SIG]);
179
180 if (type == MBR_TYPE_LOADER || type == MBR_TYPE_LOADER_JOYENT) {
181 char uuid[UUID_PRINTABLE_STRING_LENGTH];
182
183 mdb_printf("Loader STAGE1_STAGE2_LBA: %llu\n",
184 *(uint64_t *)&mbr.bootinst[STAGE1_STAGE2_LBA]);
185
186 mdb_printf("Loader STAGE1_STAGE2_SIZE: %hu\n",
187 *(uint16_t *)&mbr.bootinst[STAGE1_STAGE2_SIZE]);
188
189 uuid_unparse((uchar_t *)&mbr.bootinst[STAGE1_STAGE2_UUID],
190 uuid);
191
192 mdb_printf("Loader STAGE1_STAGE2_UUID: %s\n", uuid);
193 }
194
195 mdb_printf("\n%<u>%-4s %-21s %-7s %-11s %-11s %-10s %-9s%</u>\n",
196 "PART", "TYPE", "ACTIVE", "STARTCHS", "ENDCHS",
197 "SECTOR", "NUMSECT");
198
199 for (size_t i = 0; i < FD_NUMPART; i++) {
200 struct ipart *ip = (struct ipart *)
201 (mbr.parts + (sizeof (struct ipart) * i));
202 print_fdisk_part(ip, i);
203 }
204
205 return (DCMD_OK);
206 }
207
208 static unsigned int crc32_tab[] = { CRC32_TABLE };
209
210 static unsigned int
211 efi_crc32(const unsigned char *s, unsigned int len)
212 {
213 unsigned int crc32val;
214
215 CRC32(crc32val, s, len, -1U, crc32_tab);
216
217 return (crc32val ^ -1U);
218 }
219
220 typedef struct {
221 struct uuid eg_uuid;
222 const char *eg_name;
223 } efi_guid_t;
224
225 static efi_guid_t efi_guids[] = {
226 { EFI_UNUSED, "EFI_UNUSED" },
227 { EFI_RESV1, "EFI_RESV1" },
228 { EFI_BOOT, "EFI_BOOT" },
229 { EFI_ROOT, "EFI_ROOT" },
230 { EFI_SWAP, "EFI_SWAP" },
231 { EFI_USR, "EFI_USR" },
232 { EFI_BACKUP, "EFI_BACKUP" },
233 { EFI_RESV2, "EFI_RESV2" },
234 { EFI_VAR, "EFI_VAR" },
235 { EFI_HOME, "EFI_HOME" },
236 { EFI_ALTSCTR, "EFI_ALTSCTR" },
237 { EFI_RESERVED, "EFI_RESERVED" },
238 { EFI_SYSTEM, "EFI_SYSTEM" },
239 { EFI_LEGACY_MBR, "EFI_LEGACY_MBR" },
240 { EFI_SYMC_PUB, "EFI_SYMC_PUB" },
241 { EFI_SYMC_CDS, "EFI_SYMC_CDS" },
242 { EFI_MSFT_RESV, "EFI_MSFT_RESV" },
243 { EFI_DELL_BASIC, "EFI_DELL_BASIC" },
244 { EFI_DELL_RAID, "EFI_DELL_RAID" },
245 { EFI_DELL_SWAP, "EFI_DELL_SWAP" },
246 { EFI_DELL_LVM, "EFI_DELL_LVM" },
247 { EFI_DELL_RESV, "EFI_DELL_RESV" },
248 { EFI_AAPL_BOOT, "EFI_AAPL_BOOT" },
249 { EFI_AAPL_HFS, "EFI_AAPL_HFS" },
250 { EFI_AAPL_UFS, "EFI_AAPL_UFS" },
251 { EFI_AAPL_ZFS, "EFI_AAPL_ZFS" },
252 { EFI_AAPL_APFS, "EFI_AAPL_APFS" },
253 { EFI_FREEBSD_BOOT, "EFI_FREEBSD_BOOT" },
254 { EFI_FREEBSD_NANDFS, "EFI_FREEBSD_NANDFS" },
255 { EFI_FREEBSD_SWAP, "EFI_FREEBSD_SWAP" },
256 { EFI_FREEBSD_UFS, "EFI_FREEBSD_UFS" },
257 { EFI_FREEBSD_VINUM, "EFI_FREEBSD_VINUM" },
258 { EFI_FREEBSD_ZFS, "EFI_FREEBSD_ZFS" },
259 { EFI_BIOS_BOOT, "EFI_BIOS_BOOT" },
260 };
261
262 static void
263 print_gpe(efi_gpe_t *gpe, size_t nr, int show_guid)
264 {
265 const char *type = "unknown";
266
267 for (size_t i = 0; i < ARRAY_SIZE(efi_guids); i++) {
268 if (memcmp((void *)&efi_guids[i].eg_uuid,
269 (void *)&gpe->efi_gpe_PartitionTypeGUID,
270 sizeof (efi_guids[i].eg_uuid)) == 0) {
271 type = efi_guids[i].eg_name;
272 break;
273 }
274 }
275
276 if (strcmp(type, "EFI_UNUSED") == 0) {
277 mdb_printf("%-4u %-19s\n", nr, type);
278 return;
279 }
280
281 if (show_guid) {
282 char guid[UUID_PRINTABLE_STRING_LENGTH];
283
284 uuid_unparse((uchar_t *)&gpe->efi_gpe_UniquePartitionGUID,
285 guid);
286
287 mdb_printf("%-4u %-19s %s\n", nr, type, guid);
288 } else {
289 char name[EFI_PART_NAME_LEN + 1] = "";
290
291 /*
292 * Hopefully, ASCII is sufficient for any naming we care about.
293 */
294 for (size_t i = 0; i < sizeof (name); i++) {
295 ushort_t wchar = gpe->efi_gpe_PartitionName[i];
296
297 name[i] = (char)(isascii(wchar) ? wchar : '?');
298 }
299
300 mdb_printf("%-4u %-19s %-13llu %-13llu %#-8llx %s\n",
301 nr, type, gpe->efi_gpe_StartingLBA, gpe->efi_gpe_EndingLBA,
302 gpe->efi_gpe_Attributes, name);
303 }
304 }
305
306 static int
307 cmd_gpt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv __unused)
308 {
309 char uuid[UUID_PRINTABLE_STRING_LENGTH];
310 int show_alternate = B_FALSE;
311 int show_guid = B_FALSE;
312 efi_gpt_t altheader;
313 size_t table_size;
314 efi_gpt_t header;
315 efi_gpe_t *gpet;
316 uint_t orig_crc;
317 uint_t crc;
318
319 if (mdb_getopts(argc, argv,
320 'a', MDB_OPT_SETBITS, TRUE, &show_alternate,
321 'g', MDB_OPT_SETBITS, TRUE, &show_guid,
322 NULL) != argc)
323 return (DCMD_USAGE);
324
325 /* Primary header is at LBA 1. */
326 if (!(flags & DCMD_ADDRSPEC))
327 addr = SECTOR_SIZE;
328
329 if (mdb_vread(&header, sizeof (header), addr) == -1) {
330 mdb_warn("failed to read GPT header");
331 return (DCMD_ERR);
332 }
333
334 if (show_alternate) {
335 addr = header.efi_gpt_AlternateLBA * SECTOR_SIZE;
336
337 if (mdb_vread(&header, sizeof (header), addr) == -1) {
338 mdb_warn("failed to read GPT header");
339 return (DCMD_ERR);
340 }
341 }
342
343 mdb_printf("Signature: %s (%s)\n", (char *)&header.efi_gpt_Signature,
344 strncmp((char *)&header.efi_gpt_Signature, "EFI PART", 8) == 0 ?
345 "valid" : "invalid");
346
347 mdb_printf("Revision: %hu.%hu\n", header.efi_gpt_Revision >> 16,
348 header.efi_gpt_Revision);
349
350 mdb_printf("HeaderSize: %u bytes\n", header.efi_gpt_HeaderSize);
351
352 if (header.efi_gpt_HeaderSize > SECTOR_SIZE) {
353 mdb_warn("invalid header size: skipping CRC\n");
354 } else {
355 orig_crc = header.efi_gpt_HeaderCRC32;
356
357 header.efi_gpt_HeaderCRC32 = 0;
358
359 crc = efi_crc32((unsigned char *)&header,
360 header.efi_gpt_HeaderSize);
361
362 mdb_printf("HeaderCRC32: %#x (should be %#x)\n", orig_crc, crc);
363 }
364
365 mdb_printf("Reserved1: %#x (should be 0x0)\n",
366 header.efi_gpt_Reserved1);
367
368 mdb_printf("MyLBA: %llu (should be %llu)\n",
369 header.efi_gpt_MyLBA, addr / SECTOR_SIZE);
370
371 mdb_printf("AlternateLBA: %llu\n", header.efi_gpt_AlternateLBA);
372 mdb_printf("FirstUsableLBA: %llu\n", header.efi_gpt_FirstUsableLBA);
373 mdb_printf("LastUsableLBA: %llu\n", header.efi_gpt_LastUsableLBA);
374
375 if (header.efi_gpt_MyLBA >= header.efi_gpt_FirstUsableLBA &&
376 header.efi_gpt_MyLBA <= header.efi_gpt_LastUsableLBA) {
377 mdb_warn("MyLBA is within usable LBA range\n");
378 }
379
380 if (header.efi_gpt_AlternateLBA >= header.efi_gpt_FirstUsableLBA &&
381 header.efi_gpt_AlternateLBA <= header.efi_gpt_LastUsableLBA) {
382 mdb_warn("AlternateLBA is within usable LBA range\n");
383 }
384
385 if (mdb_vread(&altheader, sizeof (altheader),
386 header.efi_gpt_AlternateLBA * SECTOR_SIZE) == -1) {
387 mdb_warn("failed to read alternate GPT header");
388 } else {
389 if (strncmp((char *)&altheader.efi_gpt_Signature,
390 "EFI PART", 8) != 0) {
391 mdb_warn("found invalid alternate GPT header with "
392 "Signature: %s\n",
393 (char *)&altheader.efi_gpt_Signature);
394 }
395
396 if (altheader.efi_gpt_MyLBA != header.efi_gpt_AlternateLBA) {
397 mdb_warn("alternate GPT header at offset %#llx has "
398 "invalid MyLBA %llu\n",
399 header.efi_gpt_AlternateLBA * SECTOR_SIZE,
400 altheader.efi_gpt_MyLBA);
401 }
402
403 if (altheader.efi_gpt_AlternateLBA != header.efi_gpt_MyLBA) {
404 mdb_warn("alternate GPT header at offset %#llx has "
405 "invalid AlternateLBA %llu\n",
406 header.efi_gpt_AlternateLBA * SECTOR_SIZE,
407 altheader.efi_gpt_AlternateLBA);
408 }
409
410 /*
411 * We could go ahead and verify all the alternate checksums,
412 * etc. here too...
413 */
414 }
415
416 uuid_unparse((uchar_t *)&header.efi_gpt_DiskGUID, uuid);
417 mdb_printf("DiskGUID: %s\n", uuid);
418
419 mdb_printf("PartitionEntryLBA: %llu\n",
420 header.efi_gpt_PartitionEntryLBA);
421
422 mdb_printf("NumberOfPartitionEntries: %u\n",
423 header.efi_gpt_NumberOfPartitionEntries);
424
425 /*
426 * While the spec allows a different size, in practice the table
427 * is always packed.
428 */
429 if (header.efi_gpt_SizeOfPartitionEntry != sizeof (efi_gpe_t)) {
430 mdb_warn("SizeOfPartitionEntry: %#x bytes "
431 "(expected %#x bytes)\n",
432 header.efi_gpt_SizeOfPartitionEntry, sizeof (efi_gpe_t));
433 return (DCMD_ERR);
434 }
435
436 mdb_printf("SizeOfPartitionEntry: %#x bytes\n",
437 header.efi_gpt_SizeOfPartitionEntry);
438
439 table_size = header.efi_gpt_SizeOfPartitionEntry *
440 header.efi_gpt_NumberOfPartitionEntries;
441
442 /*
443 * While this is a minimum reservation, it serves us ably as a
444 * maximum value to reasonably expect.
445 */
446 if (table_size > EFI_MIN_ARRAY_SIZE) {
447 mdb_warn("Skipping GPT array of %#lx bytes.\n", table_size);
448 return (DCMD_ERR);
449 }
450
451 gpet = mdb_alloc(header.efi_gpt_SizeOfPartitionEntry *
452 header.efi_gpt_NumberOfPartitionEntries, UM_SLEEP | UM_GC);
453
454 if (mdb_vread(gpet, table_size,
455 header.efi_gpt_PartitionEntryLBA * SECTOR_SIZE) == -1) {
456 mdb_warn("couldn't read GPT array");
457 return (DCMD_ERR);
458 }
459
460 crc = efi_crc32((unsigned char *)gpet, table_size);
461
462 mdb_printf("PartitionEntryArrayCRC32: %#x (should be %#x)\n",
463 header.efi_gpt_PartitionEntryArrayCRC32, crc);
464
465 if (show_guid) {
466 mdb_printf("\n%<u>%-4s %-19s %-37s%</u>\n",
467 "PART", "TYPE", "GUID");
468 } else {
469 mdb_printf("\n%<u>%-4s %-19s %-13s %-13s %-8s %s%</u>\n",
470 "PART", "TYPE", "STARTLBA", "ENDLBA", "ATTR", "NAME");
471 }
472
473 for (size_t i = 0; i < header.efi_gpt_NumberOfPartitionEntries; i++)
474 print_gpe(&gpet[i], i, show_guid);
475
476 return (DCMD_OK);
477 }
478
479 void
480 gpt_help(void)
481 {
482 mdb_printf("Display an EFI GUID Partition Table.\n\n"
483 "-a Display the alternate GPT\n"
484 "-g Show unique GUID for each table entry\n");
485 }
486
487 static const mdb_dcmd_t dcmds[] = {
488 { "mbr", NULL, "dump Master Boot Record information", cmd_mbr },
489 { "gpt", "?[-ag]", "dump an EFI GPT", cmd_gpt, gpt_help },
490 { NULL }
491 };
492
493 static const mdb_modinfo_t modinfo = {
494 MDB_API_VERSION, dcmds, NULL
495 };
496
497 const mdb_modinfo_t *
498 _mdb_init(void)
499 {
500 return (&modinfo);
501 }