diff --git a/.gitignore b/.gitignore index 819cd185d..b8efcc4dd 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ gensymlist.sh gentrigtables gentrigtables.exe gettext_strings_test +gpt_unit_test /gnulib grub-bin2h /grub-bios-setup diff --git a/Makefile.util.def b/Makefile.util.def index 969d32f00..65cbfc081 100644 --- a/Makefile.util.def +++ b/Makefile.util.def @@ -1175,6 +1175,18 @@ script = { common = tests/grub_cmd_tr.in; }; +script = { + testcase; + name = gptrepair_test; + common = tests/gptrepair_test.in; +}; + +script = { + testcase; + name = gptprio_test; + common = tests/gptprio_test.in; +}; + script = { testcase; name = file_filter_test; @@ -1270,6 +1282,25 @@ program = { ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)'; }; +program = { + testcase; + name = gpt_unit_test; + common = tests/gpt_unit_test.c; + common = tests/lib/unit_test.c; + common = grub-core/commands/search_part_label.c; + common = grub-core/commands/search_part_uuid.c; + common = grub-core/commands/search_disk_uuid.c; + common = grub-core/disk/host.c; + common = grub-core/kern/emu/hostfs.c; + common = grub-core/lib/gpt.c; + common = grub-core/tests/lib/test.c; + ldadd = libgrubmods.a; + ldadd = libgrubgcry.a; + ldadd = libgrubkern.a; + ldadd = grub-core/gnulib/libgnu.a; + ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)'; +}; + program = { name = grub-menulst2cfg; mansection = 1; diff --git a/docs/grub.texi b/docs/grub.texi index 87795075a..54b0e17ae 100644 --- a/docs/grub.texi +++ b/docs/grub.texi @@ -3944,6 +3944,7 @@ you forget a command, you can run the command @command{help} * sha256sum:: Compute or check SHA256 hash * sha512sum:: Compute or check SHA512 hash * sleep:: Wait for a specified number of seconds +* smbios:: Retrieve SMBIOS information * source:: Read a configuration file in same context * test:: Check file types and compare values * true:: Do nothing, successfully @@ -5083,6 +5084,74 @@ if timeout was interrupted by @key{ESC}. @end deffn +@node smbios +@subsection smbios + +@deffn Command smbios @ + [@option{--type} @var{type}] @ + [@option{--handle} @var{handle}] @ + [@option{--match} @var{match}] @ + (@option{--get-byte} | @option{--get-word} | @option{--get-dword} | @ + @option{--get-qword} | @option{--get-string} | @option{--get-uuid}) @ + @var{offset} @ + [@option{--set} @var{variable}] +Retrieve SMBIOS information. + +The @command{smbios} command returns the value of a field in an SMBIOS +structure. The following options determine which structure to select. + +@itemize @bullet +@item +Specifying @option{--type} will select structures with a matching +@var{type}. The type can be any integer from 0 to 255. +@item +Specifying @option{--handle} will select structures with a matching +@var{handle}. The handle can be any integer from 0 to 65535. +@item +Specifying @option{--match} will select structure number @var{match} in the +filtered list of structures; e.g. @code{smbios --type 4 --match 2} will select +the second Process Information (Type 4) structure. The list is always ordered +the same as the hardware's SMBIOS table. The match number must be a positive +integer. If unspecified, the first matching structure will be selected. +@end itemize + +The remaining options determine which field in the selected SMBIOS structure to +return. Only one of these options may be specified at a time. + +@itemize @bullet +@item +When given @option{--get-byte}, return the value of the byte +at @var{offset} bytes into the selected SMBIOS structure. +@item +When given @option{--get-word}, return the value of the word (two bytes) +at @var{offset} bytes into the selected SMBIOS structure. +@item +When given @option{--get-dword}, return the value of the dword (four bytes) +at @var{offset} bytes into the selected SMBIOS structure. +@item +When given @option{--get-qword}, return the value of the qword (eight bytes) +at @var{offset} bytes into the selected SMBIOS structure. +@item +When given @option{--get-string}, return the string with its index found +at @var{offset} bytes into the selected SMBIOS structure. +@item +When given @option{--get-uuid}, return the value of the UUID (sixteen bytes) +at @var{offset} bytes into the selected SMBIOS structure. +@end itemize + +The default action is to print the value of the requested field to the console, +but a variable name can be specified with @option{--set} to store the value +instead of printing it. + +For example, this will store and then display the system manufacturer's name. + +@example +smbios --type 1 --get-string 4 --set system_manufacturer +echo $system_manufacturer +@end example +@end deffn + + @node source @subsection source diff --git a/grub-core/Makefile.am b/grub-core/Makefile.am index 3ea8e7ff4..5dc520bd4 100644 --- a/grub-core/Makefile.am +++ b/grub-core/Makefile.am @@ -92,6 +92,7 @@ KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/term.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/time.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/mm_private.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/net.h +KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/tpm.h KERNEL_HEADER_FILES += $(top_srcdir)/include/grub/memory.h if COND_i386_pc diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def index 474a63e68..6ca83ec39 100644 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@ -140,6 +140,7 @@ kernel = { common = kern/rescue_parser.c; common = kern/rescue_reader.c; common = kern/term.c; + common = kern/tpm.c; noemu = kern/compiler-rt.c; noemu = kern/mm.c; @@ -203,6 +204,7 @@ kernel = { efi = term/efi/console.c; efi = kern/acpi.c; efi = kern/efi/acpi.c; + efi = kern/efi/tpm.c; i386_coreboot = kern/i386/pc/acpi.c; i386_multiboot = kern/i386/pc/acpi.c; i386_coreboot = kern/acpi.c; @@ -262,6 +264,7 @@ kernel = { i386_pc = kern/i386/pc/init.c; i386_pc = kern/i386/pc/mmap.c; + i386_pc = kern/i386/pc/tpm.c; i386_pc = term/i386/pc/console.c; i386_qemu = bus/pci.c; @@ -888,11 +891,32 @@ module = { enable = x86_64_efi; }; +module = { + name = getenv; + common = commands/efi/getenv.c; + enable = efi; +}; + module = { name = gptsync; common = commands/gptsync.c; }; +module = { + name = gptrepair; + common = commands/gptrepair.c; +}; + +module = { + name = gptprio; + common = commands/gptprio.c; +}; + +module = { + name = gpt; + common = lib/gpt.c; +}; + module = { name = halt; nopc = commands/halt.c; @@ -1080,6 +1104,21 @@ module = { common = commands/search_label.c; }; +module = { + name = search_part_uuid; + common = commands/search_part_uuid.c; +}; + +module = { + name = search_part_label; + common = commands/search_part_label.c; +}; + +module = { + name = search_disk_uuid; + common = commands/search_disk_uuid.c; +}; + module = { name = setpci; common = commands/setpci.c; @@ -1097,6 +1136,21 @@ module = { common = commands/sleep.c; }; +module = { + name = smbios; + + common = commands/smbios.c; + efi = commands/efi/smbios.c; + i386_pc = commands/i386/pc/smbios.c; + i386_coreboot = commands/i386/pc/smbios.c; + i386_multiboot = commands/i386/pc/smbios.c; + + enable = efi; + enable = i386_pc; + enable = i386_coreboot; + enable = i386_multiboot; +}; + module = { name = suspend; ieee1275 = commands/ieee1275/suspend.c; @@ -1849,6 +1903,14 @@ module = { enable = x86_64_efi; }; +module = { + name = linuxefi; + efi = loader/i386/efi/linux.c; + efi = lib/cmdline.c; + enable = i386_efi; + enable = x86_64_efi; +}; + module = { name = chain; efi = loader/efi/chainloader.c; @@ -2503,3 +2565,8 @@ module = { common = commands/i386/wrmsr.c; enable = x86; }; +module = { + name = fwconfig; + common = commands/fwconfig.c; + enable = x86; +}; diff --git a/grub-core/boot/i386/pc/boot.S b/grub-core/boot/i386/pc/boot.S index 2bd0b2d28..47a461ed5 100644 --- a/grub-core/boot/i386/pc/boot.S +++ b/grub-core/boot/i386/pc/boot.S @@ -24,11 +24,14 @@ * defines for the code go here */ +#define TPM 1 + /* Print message string */ #define MSG(x) movw $x, %si; call LOCAL(message) #define ERR(x) movw $x, %si; jmp LOCAL(error_message) .macro floppy +#ifndef TPM part_start: LOCAL(probe_values): @@ -85,6 +88,7 @@ fd_probe_error_string: .asciz "Floppy" movb MACRO_DOLLAR(79), %ch jmp LOCAL(final_init) +#endif .endm .macro scratch @@ -255,6 +259,7 @@ real_start: /* set %si to the disk address packet */ movw $disk_address_packet, %si +#ifndef TPM /* check if LBA is supported */ movb $0x41, %ah movw $0x55aa, %bx @@ -274,6 +279,7 @@ real_start: andw $1, %cx jz LOCAL(chs_mode) +#endif LOCAL(lba_mode): xorw %ax, %ax @@ -317,6 +323,9 @@ LOCAL(lba_mode): jmp LOCAL(copy_buffer) LOCAL(chs_mode): +#ifdef TPM + jmp LOCAL(general_error) +#else /* * Determine the hard disk geometry from the BIOS! * We do this first, so that LS-120 IDE floppies work correctly. @@ -428,7 +437,7 @@ setup_sectors: jc LOCAL(read_error) movw %es, %bx - +#endif /* TPM */ LOCAL(copy_buffer): /* * We need to save %cx and %si because the startup code in @@ -451,6 +460,25 @@ LOCAL(copy_buffer): popw %ds popa +#ifdef TPM + pusha + + movw $0xBB00, %ax /* TCG_StatusCheck */ + int $0x1A + test %eax, %eax + jnz boot /* No TPM or TPM deactivated */ + + movw $0xBB07, %ax /* TCG_CompactHashLogExtendEvent */ + movw $GRUB_BOOT_MACHINE_KERNEL_ADDR, %di + xorl %esi, %esi + movl $0x41504354, %ebx /* TCPA */ + movl $0x200, %ecx /* Measure 512 bytes */ + movl $0x8, %edx /* PCR 8 */ + int $0x1A + +boot: + popa +#endif /* boot kernel */ jmp *(LOCAL(kernel_address)) diff --git a/grub-core/boot/i386/pc/diskboot.S b/grub-core/boot/i386/pc/diskboot.S index c1addc0df..5d413d50d 100644 --- a/grub-core/boot/i386/pc/diskboot.S +++ b/grub-core/boot/i386/pc/diskboot.S @@ -19,6 +19,8 @@ #include #include +#define TPM 1 + /* * defines for the code go here */ @@ -58,6 +60,21 @@ _start: /* this sets up for the first run through "bootloop" */ movw $LOCAL(firstlist), %di +#ifdef TPM + /* clear EAX to remove potential garbage */ + xorl %eax, %eax + /* 8(%di) = number of sectors to read */ + movw 8(%di), %ax + + /* Multiply number of sectors to read with 512 bytes. EAX is 32bit + * which is large enough to hold values of up to 4GB. I doubt there + * will ever be a core.img larger than that. ;-) */ + shll $9, %eax + + /* write result to bytes_to_measure var */ + movl %eax, bytes_to_measure +#endif + /* save the sector number of the second sector in %ebp */ movl (%di), %ebp @@ -295,6 +312,29 @@ LOCAL(copy_buffer): /* END OF MAIN LOOP */ LOCAL(bootit): +#ifdef TPM + pusha + movw $0xBB07, %ax /* TCG_CompactHashLogExtendEvent */ + + movw $0x0, %bx + movw %bx, %es + + /* We've already measured the first 512 bytes, now measure the rest */ + xorl %edi, %edi + movw $(GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200), %di + + movl $0x41504354, %ebx /* EBX = "TCPA" */ + + /* %ecx = The length, in bytes, of the buffer to measure */ + movl $bytes_to_measure, %esi + movl (%esi), %ecx + xorl %esi, %esi + movl $0x9, %edx /* PCR 9 */ + + int $0x1A + + popa +#endif /* print a newline */ MSG(notification_done) popw %dx /* this makes sure %dl is our "boot" drive */ @@ -329,6 +369,10 @@ geometry_error_string: .asciz "Geom" read_error_string: .asciz "Read" general_error_string: .asciz " Error" +#ifdef TPM +bytes_to_measure: .long 0 +#endif + /* * message: write the string pointed to by %si * diff --git a/grub-core/commands/efi/getenv.c b/grub-core/commands/efi/getenv.c new file mode 100644 index 000000000..7eb2c4506 --- /dev/null +++ b/grub-core/commands/efi/getenv.c @@ -0,0 +1,153 @@ +/* getenv.c - retrieve EFI variables. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * Copyright (C) 2014 CoreOS, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static const struct grub_arg_option options_getenv[] = { + {"var-name", 'e', 0, + N_("Environment variable to query"), + N_("VARNAME"), ARG_TYPE_STRING}, + {"var-guid", 'g', 0, + N_("GUID of environment variable to query"), + N_("GUID"), ARG_TYPE_STRING}, + {"binary", 'b', 0, + N_("Read binary data and represent it as hex"), + 0, ARG_TYPE_NONE}, + {0, 0, 0, 0, 0, 0} +}; + +enum options_getenv +{ + GETENV_VAR_NAME, + GETENV_VAR_GUID, + GETENV_BINARY, +}; + +static grub_err_t +grub_cmd_getenv (grub_extcmd_context_t ctxt, int argc, char **args) +{ + struct grub_arg_list *state = ctxt->state; + char *envvar = NULL, *guid = NULL, *bindata = NULL, *data = NULL; + grub_size_t datasize; + grub_efi_guid_t efi_var_guid; + grub_efi_boolean_t binary = state[GETENV_BINARY].set; + unsigned int i; + + if (!state[GETENV_VAR_NAME].set || !state[GETENV_VAR_GUID].set) + { + grub_error (GRUB_ERR_INVALID_COMMAND, N_("-e and -g are required")); + goto done; + } + + if (argc != 1) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unexpected arguments")); + goto done; + } + + envvar = state[GETENV_VAR_NAME].arg; + guid = state[GETENV_VAR_GUID].arg; + + if (grub_strlen(guid) != 36 || + guid[8] != '-' || + guid[13] != '-' || + guid[18] != '-' || + guid[23] != '-') + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("invalid GUID")); + goto done; + } + + /* Forgive me father for I have sinned */ + guid[8] = 0; + efi_var_guid.data1 = grub_strtoul(guid, NULL, 16); + guid[13] = 0; + efi_var_guid.data2 = grub_strtoul(guid + 9, NULL, 16); + guid[18] = 0; + efi_var_guid.data3 = grub_strtoul(guid + 14, NULL, 16); + efi_var_guid.data4[7] = grub_strtoul(guid + 34, NULL, 16); + guid[34] = 0; + efi_var_guid.data4[6] = grub_strtoul(guid + 32, NULL, 16); + guid[32] = 0; + efi_var_guid.data4[5] = grub_strtoul(guid + 30, NULL, 16); + guid[30] = 0; + efi_var_guid.data4[4] = grub_strtoul(guid + 28, NULL, 16); + guid[28] = 0; + efi_var_guid.data4[3] = grub_strtoul(guid + 26, NULL, 16); + guid[26] = 0; + efi_var_guid.data4[2] = grub_strtoul(guid + 24, NULL, 16); + guid[23] = 0; + efi_var_guid.data4[1] = grub_strtoul(guid + 21, NULL, 16); + guid[21] = 0; + efi_var_guid.data4[0] = grub_strtoul(guid + 19, NULL, 16); + + data = grub_efi_get_variable(envvar, &efi_var_guid, &datasize); + + if (!data || !datasize) + { + grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("No such variable")); + goto done; + } + + if (binary) + { + bindata = grub_zalloc(datasize * 2 + 1); + for (i=0; inum_table_entries; i++) { grub_efi_packed_guid_t *guid = @@ -127,6 +129,11 @@ fake_bios_data (int use_rom) smbios = grub_efi_system_table->configuration_table[i].vendor_table; grub_dprintf ("efi", "SMBIOS: %p\n", smbios); } + else if (! grub_memcmp (guid, &smbios3_guid, sizeof (grub_efi_guid_t))) + { + smbios3 = grub_efi_system_table->configuration_table[i].vendor_table; + grub_dprintf ("efi", "SMBIOS3: %p\n", smbios3); + } } *ebda_seg_ptr = FAKE_EBDA_SEG; @@ -137,8 +144,13 @@ fake_bios_data (int use_rom) if (acpi) grub_memcpy ((char *) ((FAKE_EBDA_SEG << 4) + 16), acpi, 1024 - 16); - if ((use_rom) && (smbios)) - grub_memcpy ((char *) SBIOS_ADDR, (char *) smbios + 16, 16); + if (use_rom) + { + if (smbios) + grub_memcpy ((char *) SBIOS_ADDR, (char *) smbios, 31); + if (smbios3) + grub_memcpy ((char *) SBIOS_ADDR + 32, (char *) smbios3, 24); + } } static grub_err_t diff --git a/grub-core/commands/efi/lsefisystab.c b/grub-core/commands/efi/lsefisystab.c index df1030221..7c039c509 100644 --- a/grub-core/commands/efi/lsefisystab.c +++ b/grub-core/commands/efi/lsefisystab.c @@ -48,6 +48,7 @@ static const struct guid_mapping guid_mappings[] = { GRUB_EFI_MPS_TABLE_GUID, "MPS"}, { GRUB_EFI_SAL_TABLE_GUID, "SAL"}, { GRUB_EFI_SMBIOS_TABLE_GUID, "SMBIOS"}, + { GRUB_EFI_SMBIOS3_TABLE_GUID, "SMBIOS3"}, { GRUB_EFI_SYSTEM_RESOURCE_TABLE_GUID, "SYSTEM RESOURCE TABLE"}, { GRUB_EFI_TIANO_CUSTOM_DECOMPRESS_GUID, "TIANO CUSTOM DECOMPRESS"}, { GRUB_EFI_TSC_FREQUENCY_GUID, "TSC FREQUENCY"}, diff --git a/grub-core/commands/efi/smbios.c b/grub-core/commands/efi/smbios.c new file mode 100644 index 000000000..927702209 --- /dev/null +++ b/grub-core/commands/efi/smbios.c @@ -0,0 +1,59 @@ +/* smbios.c - get smbios tables. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2015 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include + +struct grub_smbios_eps * +grub_machine_smbios_get_eps (void) +{ + unsigned i; + static grub_efi_packed_guid_t smbios_guid = GRUB_EFI_SMBIOS_TABLE_GUID; + + for (i = 0; i < grub_efi_system_table->num_table_entries; i++) + { + grub_efi_packed_guid_t *guid = + &grub_efi_system_table->configuration_table[i].vendor_guid; + + if (! grub_memcmp (guid, &smbios_guid, sizeof (grub_efi_packed_guid_t))) + return (struct grub_smbios_eps *) + grub_efi_system_table->configuration_table[i].vendor_table; + } + return 0; +} + +struct grub_smbios_eps3 * +grub_machine_smbios_get_eps3 (void) +{ + unsigned i; + static grub_efi_packed_guid_t smbios3_guid = GRUB_EFI_SMBIOS3_TABLE_GUID; + + for (i = 0; i < grub_efi_system_table->num_table_entries; i++) + { + grub_efi_packed_guid_t *guid = + &grub_efi_system_table->configuration_table[i].vendor_guid; + + if (! grub_memcmp (guid, &smbios3_guid, sizeof (grub_efi_packed_guid_t))) + return (struct grub_smbios_eps3 *) + grub_efi_system_table->configuration_table[i].vendor_table; + } + return 0; +} diff --git a/grub-core/commands/fwconfig.c b/grub-core/commands/fwconfig.c new file mode 100644 index 000000000..3abe59f2b --- /dev/null +++ b/grub-core/commands/fwconfig.c @@ -0,0 +1,122 @@ +/* fwconfig.c - command to read config from qemu fwconfig */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2015 CoreOS, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +#define SELECTOR 0x510 +#define DATA 0x511 + +#define SIGNATURE_INDEX 0x00 +#define DIRECTORY_INDEX 0x19 + +static grub_extcmd_t cmd_read_fwconfig; + +struct grub_qemu_fwcfgfile { + grub_uint32_t size; + grub_uint16_t select; + grub_uint16_t reserved; + char name[56]; +}; + +static const struct grub_arg_option options[] = + { + {0, 'v', 0, N_("Save read value into variable VARNAME."), + N_("VARNAME"), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} + }; + +static grub_err_t +grub_cmd_fwconfig (grub_extcmd_context_t ctxt __attribute__ ((unused)), + int argc, char **argv) +{ + grub_uint32_t i, j, value = 0; + struct grub_qemu_fwcfgfile file; + char fwsig[4], signature[4] = { 'Q', 'E', 'M', 'U' }; + + if (argc != 2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("two arguments expected")); + + /* Verify that we have meaningful hardware here */ + grub_outw(SIGNATURE_INDEX, SELECTOR); + for (i=0; i. + */ + +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static const struct grub_arg_option options_next[] = { + {"set-device", 'd', 0, + N_("Set a variable to the name of selected partition."), + N_("VARNAME"), ARG_TYPE_STRING}, + {"set-uuid", 'u', 0, + N_("Set a variable to the GPT UUID of selected partition."), + N_("VARNAME"), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} +}; + +enum options_next +{ + NEXT_SET_DEVICE, + NEXT_SET_UUID, +}; + +static unsigned int +grub_gptprio_priority (struct grub_gpt_partentry *entry) +{ + return (unsigned int) grub_gpt_entry_attribute + (entry, GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_PRIORITY, 4); +} + +static unsigned int +grub_gptprio_tries_left (struct grub_gpt_partentry *entry) +{ + return (unsigned int) grub_gpt_entry_attribute + (entry, GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_TRIES_LEFT, 4); +} + +static void +grub_gptprio_set_tries_left (struct grub_gpt_partentry *entry, + unsigned int tries_left) +{ + grub_gpt_entry_set_attribute + (entry, tries_left, GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_TRIES_LEFT, 4); +} + +static unsigned int +grub_gptprio_successful (struct grub_gpt_partentry *entry) +{ + return (unsigned int) grub_gpt_entry_attribute + (entry, GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_SUCCESSFUL, 1); +} + +static grub_err_t +grub_find_next (const char *disk_name, + const grub_gpt_part_guid_t *part_type, + char **part_name, char **part_guid) +{ + struct grub_gpt_partentry *part, *part_found = NULL; + grub_device_t dev = NULL; + grub_gpt_t gpt = NULL; + grub_uint32_t i, part_index; + + dev = grub_device_open (disk_name); + if (!dev) + goto done; + + gpt = grub_gpt_read (dev->disk); + if (!gpt) + goto done; + + if (grub_gpt_repair (dev->disk, gpt)) + goto done; + + for (i = 0; (part = grub_gpt_get_partentry (gpt, i)) != NULL; i++) + { + if (grub_memcmp (part_type, &part->type, sizeof (*part_type)) == 0) + { + unsigned int priority, tries_left, successful, old_priority = 0; + + priority = grub_gptprio_priority (part); + tries_left = grub_gptprio_tries_left (part); + successful = grub_gptprio_successful (part); + + if (part_found) + old_priority = grub_gptprio_priority (part_found); + + if ((tries_left || successful) && priority > old_priority) + { + part_index = i; + part_found = part; + } + } + } + + if (!part_found) + { + grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("no such partition")); + goto done; + } + + if (grub_gptprio_tries_left (part_found)) + { + unsigned int tries_left = grub_gptprio_tries_left (part_found); + + grub_gptprio_set_tries_left (part_found, tries_left - 1); + + if (grub_gpt_update (gpt)) + goto done; + + if (grub_gpt_write (dev->disk, gpt)) + goto done; + } + + *part_name = grub_xasprintf ("%s,gpt%u", disk_name, part_index + 1); + if (!*part_name) + goto done; + + *part_guid = grub_gpt_guid_to_str (&part_found->guid); + if (!*part_guid) + goto done; + + grub_errno = GRUB_ERR_NONE; + +done: + grub_gpt_free (gpt); + + if (dev) + grub_device_close (dev); + + return grub_errno; +} + + + +static grub_err_t +grub_cmd_next (grub_extcmd_context_t ctxt, int argc, char **args) +{ + struct grub_arg_list *state = ctxt->state; + char *p, *root = NULL, *part_name = NULL, *part_guid = NULL; + + /* TODO: Add a uuid parser and a command line flag for providing type. */ + grub_gpt_part_guid_t part_type = GRUB_GPT_PARTITION_TYPE_USR_X86_64; + + if (!state[NEXT_SET_DEVICE].set || !state[NEXT_SET_UUID].set) + { + grub_error (GRUB_ERR_INVALID_COMMAND, N_("-d and -u are required")); + goto done; + } + + if (argc == 0) + root = grub_strdup (grub_env_get ("root")); + else if (argc == 1) + root = grub_strdup (args[0]); + else + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unexpected arguments")); + goto done; + } + + if (!root) + goto done; + + /* To make using $root practical strip off the partition name. */ + p = grub_strchr (root, ','); + if (p) + *p = '\0'; + + if (grub_find_next (root, &part_type, &part_name, &part_guid)) + goto done; + + if (grub_env_set (state[NEXT_SET_DEVICE].arg, part_name)) + goto done; + + if (grub_env_set (state[NEXT_SET_UUID].arg, part_guid)) + goto done; + + grub_errno = GRUB_ERR_NONE; + +done: + grub_free (root); + grub_free (part_name); + grub_free (part_guid); + + return grub_errno; +} + +static grub_extcmd_t cmd_next; + +GRUB_MOD_INIT(gptprio) +{ + cmd_next = grub_register_extcmd ("gptprio.next", grub_cmd_next, 0, + N_("-d VARNAME -u VARNAME [DEVICE]"), + N_("Select next partition to boot."), + options_next); +} + +GRUB_MOD_FINI(gptprio) +{ + grub_unregister_extcmd (cmd_next); +} diff --git a/grub-core/commands/gptrepair.c b/grub-core/commands/gptrepair.c new file mode 100644 index 000000000..c17c7346c --- /dev/null +++ b/grub-core/commands/gptrepair.c @@ -0,0 +1,110 @@ +/* gptrepair.c - verify and restore GPT info from alternate location. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * Copyright (C) 2014 CoreOS, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static char * +trim_dev_name (char *name) +{ + grub_size_t len = grub_strlen (name); + if (len && name[0] == '(' && name[len - 1] == ')') + { + name[len - 1] = '\0'; + name = name + 1; + } + return name; +} + +static grub_err_t +grub_cmd_gptrepair (grub_command_t cmd __attribute__ ((unused)), + int argc, char **args) +{ + grub_device_t dev = NULL; + grub_gpt_t gpt = NULL; + char *dev_name; + + if (argc != 1 || !grub_strlen(args[0])) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name required"); + + dev_name = trim_dev_name (args[0]); + dev = grub_device_open (dev_name); + if (!dev) + goto done; + + if (!dev->disk) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, "not a disk"); + goto done; + } + + gpt = grub_gpt_read (dev->disk); + if (!gpt) + goto done; + + if (grub_gpt_both_valid (gpt)) + { + grub_printf_ (N_("GPT already valid, %s unmodified.\n"), dev_name); + goto done; + } + + if (!grub_gpt_primary_valid (gpt)) + grub_printf_ (N_("Found invalid primary GPT on %s\n"), dev_name); + + if (!grub_gpt_backup_valid (gpt)) + grub_printf_ (N_("Found invalid backup GPT on %s\n"), dev_name); + + if (grub_gpt_repair (dev->disk, gpt)) + goto done; + + if (grub_gpt_write (dev->disk, gpt)) + goto done; + + grub_printf_ (N_("Repaired GPT on %s\n"), dev_name); + +done: + if (gpt) + grub_gpt_free (gpt); + + if (dev) + grub_device_close (dev); + + return grub_errno; +} + +static grub_command_t cmd; + +GRUB_MOD_INIT(gptrepair) +{ + cmd = grub_register_command ("gptrepair", grub_cmd_gptrepair, + N_("DEVICE"), + N_("Verify and repair GPT on drive DEVICE.")); +} + +GRUB_MOD_FINI(gptrepair) +{ + grub_unregister_command (cmd); +} diff --git a/grub-core/commands/i386/pc/smbios.c b/grub-core/commands/i386/pc/smbios.c new file mode 100644 index 000000000..3bdf3d766 --- /dev/null +++ b/grub-core/commands/i386/pc/smbios.c @@ -0,0 +1,50 @@ +/* smbios.c - get smbios tables. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2015 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include + +struct grub_smbios_eps * +grub_machine_smbios_get_eps (void) +{ + grub_uint8_t *ptr; + + grub_dprintf ("smbios", "Looking for SMBIOS EPS. Scanning BIOS\n"); + for (ptr = (grub_uint8_t *) 0xf0000; ptr < (grub_uint8_t *) 0x100000; + ptr += 16) + if (grub_memcmp (ptr, "_SM_", 4) == 0 + && grub_byte_checksum (ptr, sizeof (struct grub_smbios_eps)) == 0) + return (struct grub_smbios_eps *) ptr; + return 0; +} + +struct grub_smbios_eps3 * +grub_machine_smbios_get_eps3 (void) +{ + grub_uint8_t *ptr; + + grub_dprintf ("smbios", "Looking for SMBIOS3 EPS. Scanning BIOS\n"); + for (ptr = (grub_uint8_t *) 0xf0000; ptr < (grub_uint8_t *) 0x100000; + ptr += 16) + if (grub_memcmp (ptr, "_SM3_", 5) == 0 + && grub_byte_checksum (ptr, sizeof (struct grub_smbios_eps3)) == 0) + return (struct grub_smbios_eps3 *) ptr; + return 0; +} diff --git a/grub-core/commands/pgp.c b/grub-core/commands/pgp.c index bbf6871fe..91a1c7c90 100644 --- a/grub-core/commands/pgp.c +++ b/grub-core/commands/pgp.c @@ -759,6 +759,78 @@ grub_cmd_trust (grub_extcmd_context_t ctxt, return GRUB_ERR_NONE; } +static grub_ssize_t +pseudo_read (struct grub_file *file, char *buf, grub_size_t len) +{ + grub_memcpy (buf, (grub_uint8_t *) file->data + file->offset, len); + return len; +} + +/* Filesystem descriptor. */ +struct grub_fs pseudo_fs = + { + .name = "pseudo", + .fs_read = pseudo_read + }; + +static grub_err_t +grub_cmd_trust_var (grub_command_t cmd __attribute__ ((unused)), + int argc, char **args) +{ + struct grub_file pseudo_file; + const char *var; + char *data; + struct grub_public_key *pk = NULL; + unsigned int i, idx0, idx1; + + if (argc < 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); + + var = grub_env_get (args[0]); + if (!var) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unknown variable")); + + data = grub_zalloc (grub_strlen (var) / 2); + if (!data) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("can't allocate memory for key")); + + /* For the want of sscanf() */ + for (i = 0; i < grub_strlen (var); i += 2) + { + if (var[i] < 0x40) + idx0 = var[i] - 0x30; + else + idx0 = var[i] - 0x57; + + if (var[i+1] < 0x40) + idx1 = var[i+1] - 0x30; + else + idx1 = var[i+1] - 0x57; + + data[i/2] = ((idx0 << 4) & 0xf0) | (idx1 & 0x0f); + } + + grub_memset (&pseudo_file, 0, sizeof (pseudo_file)); + + pseudo_file.fs = &pseudo_fs; + pseudo_file.size = grub_strlen (var) / 2; + pseudo_file.data = data; + + pk = grub_load_public_key (&pseudo_file); + if (!pk) + { + grub_free(data); + return grub_errno; + } + + pk->next = grub_pk_trusted; + grub_pk_trusted = pk; + + grub_free(data); + return GRUB_ERR_NONE; + +} + static grub_err_t grub_cmd_list (grub_command_t cmd __attribute__ ((unused)), int argc __attribute__ ((unused)), @@ -922,21 +994,6 @@ grub_env_write_sec (struct grub_env_var *var __attribute__ ((unused)), return grub_strdup (sec ? "enforce" : "no"); } -static grub_ssize_t -pseudo_read (struct grub_file *file, char *buf, grub_size_t len) -{ - grub_memcpy (buf, (grub_uint8_t *) file->data + file->offset, len); - return len; -} - - -/* Filesystem descriptor. */ -struct grub_fs pseudo_fs = - { - .name = "pseudo", - .fs_read = pseudo_read - }; - struct grub_file_verifier grub_pubkey_verifier = { .name = "pgp", @@ -947,7 +1004,7 @@ struct grub_file_verifier grub_pubkey_verifier = }; static grub_extcmd_t cmd, cmd_trust; -static grub_command_t cmd_distrust, cmd_list; +static grub_command_t cmd_trust_var, cmd_distrust, cmd_list; GRUB_MOD_INIT(pgp) { @@ -998,6 +1055,9 @@ GRUB_MOD_INIT(pgp) N_("[-s|--skip-sig] PUBKEY_FILE"), N_("Add PUBKEY_FILE to trusted keys."), options); + cmd_trust_var = grub_register_command ("trust_var", grub_cmd_trust_var, + N_("PUBKEY_VAR"), + N_("Add the contents of PUBKEY_VAR to trusted keys.")); cmd_list = grub_register_command ("list_trusted", grub_cmd_list, 0, N_("Show the list of trusted keys.")); @@ -1013,6 +1073,7 @@ GRUB_MOD_FINI(pgp) grub_verifier_unregister (&grub_pubkey_verifier); grub_unregister_extcmd (cmd); grub_unregister_extcmd (cmd_trust); + grub_unregister_command (cmd_trust_var); grub_unregister_command (cmd_list); grub_unregister_command (cmd_distrust); } diff --git a/grub-core/commands/search.c b/grub-core/commands/search.c index ed090b3af..fd411ce3e 100644 --- a/grub-core/commands/search.c +++ b/grub-core/commands/search.c @@ -30,6 +30,10 @@ #include #include #include +#if defined(DO_SEARCH_PART_UUID) || defined(DO_SEARCH_PART_LABEL) || \ + defined(DO_SEARCH_DISK_UUID) +#include +#endif GRUB_MOD_LICENSE ("GPLv3+"); @@ -66,7 +70,7 @@ iterate_device (const char *name, void *data) name[0] == 'f' && name[1] == 'd' && name[2] >= '0' && name[2] <= '9') return 1; -#ifdef DO_SEARCH_FS_UUID +#if defined(DO_SEARCH_FS_UUID) || defined(DO_SEARCH_DISK_UUID) #define compare_fn grub_strcasecmp #else #define compare_fn grub_strcmp @@ -90,6 +94,63 @@ iterate_device (const char *name, void *data) } grub_free (buf); } +#elif defined(DO_SEARCH_PART_UUID) + { + grub_device_t dev; + char *quid; + + dev = grub_device_open (name); + if (dev) + { + if (grub_gpt_part_uuid (dev, &quid) == GRUB_ERR_NONE) + { + if (grub_strcasecmp (quid, ctx->key) == 0) + found = 1; + + grub_free (quid); + } + + grub_device_close (dev); + } + } +#elif defined(DO_SEARCH_PART_LABEL) + { + grub_device_t dev; + char *quid; + + dev = grub_device_open (name); + if (dev) + { + if (grub_gpt_part_label (dev, &quid) == GRUB_ERR_NONE) + { + if (grub_strcmp (quid, ctx->key) == 0) + found = 1; + + grub_free (quid); + } + + grub_device_close (dev); + } + } +#elif defined(DO_SEARCH_DISK_UUID) + { + grub_device_t dev; + char *quid; + + dev = grub_device_open (name); + if (dev) + { + if (grub_gpt_disk_uuid (dev, &quid) == GRUB_ERR_NONE) + { + if (grub_strcmp (quid, ctx->key) == 0) + found = 1; + + grub_free (quid); + } + + grub_device_close (dev); + } + } #else { /* SEARCH_FS_UUID or SEARCH_LABEL */ @@ -313,8 +374,14 @@ static grub_command_t cmd; #ifdef DO_SEARCH_FILE GRUB_MOD_INIT(search_fs_file) +#elif defined(DO_SEARCH_PART_UUID) +GRUB_MOD_INIT(search_part_uuid) +#elif defined(DO_SEARCH_PART_LABEL) +GRUB_MOD_INIT(search_part_label) #elif defined (DO_SEARCH_FS_UUID) GRUB_MOD_INIT(search_fs_uuid) +#elif defined (DO_SEARCH_DISK_UUID) +GRUB_MOD_INIT(search_disk_uuid) #else GRUB_MOD_INIT(search_label) #endif @@ -327,8 +394,14 @@ GRUB_MOD_INIT(search_label) #ifdef DO_SEARCH_FILE GRUB_MOD_FINI(search_fs_file) +#elif defined(DO_SEARCH_PART_UUID) +GRUB_MOD_FINI(search_part_uuid) +#elif defined(DO_SEARCH_PART_LABEL) +GRUB_MOD_FINI(search_part_label) #elif defined (DO_SEARCH_FS_UUID) GRUB_MOD_FINI(search_fs_uuid) +#elif defined (DO_SEARCH_DISK_UUID) +GRUB_MOD_FINI(search_disk_uuid) #else GRUB_MOD_FINI(search_label) #endif diff --git a/grub-core/commands/search_disk_uuid.c b/grub-core/commands/search_disk_uuid.c new file mode 100644 index 000000000..fba96f6b8 --- /dev/null +++ b/grub-core/commands/search_disk_uuid.c @@ -0,0 +1,5 @@ +#define DO_SEARCH_DISK_UUID 1 +#define FUNC_NAME grub_search_disk_uuid +#define COMMAND_NAME "search.disk_uuid" +#define HELP_MESSAGE N_("Search devices by disk UUID. If VARIABLE is specified, the first device found is set to a variable.") +#include "search.c" diff --git a/grub-core/commands/search_part_label.c b/grub-core/commands/search_part_label.c new file mode 100644 index 000000000..ca906cbd9 --- /dev/null +++ b/grub-core/commands/search_part_label.c @@ -0,0 +1,5 @@ +#define DO_SEARCH_PART_LABEL 1 +#define FUNC_NAME grub_search_part_label +#define COMMAND_NAME "search.part_label" +#define HELP_MESSAGE N_("Search devices by partition label. If VARIABLE is specified, the first device found is set to a variable.") +#include "search.c" diff --git a/grub-core/commands/search_part_uuid.c b/grub-core/commands/search_part_uuid.c new file mode 100644 index 000000000..2d1d3d0d7 --- /dev/null +++ b/grub-core/commands/search_part_uuid.c @@ -0,0 +1,5 @@ +#define DO_SEARCH_PART_UUID 1 +#define FUNC_NAME grub_search_part_uuid +#define COMMAND_NAME "search.part_uuid" +#define HELP_MESSAGE N_("Search devices by partition UUID. If VARIABLE is specified, the first device found is set to a variable.") +#include "search.c" diff --git a/grub-core/commands/search_wrap.c b/grub-core/commands/search_wrap.c index d7fd26b94..d931c56c5 100644 --- a/grub-core/commands/search_wrap.c +++ b/grub-core/commands/search_wrap.c @@ -36,6 +36,12 @@ static const struct grub_arg_option options[] = 0, 0}, {"fs-uuid", 'u', 0, N_("Search devices by a filesystem UUID."), 0, 0}, + {"part-label", 'L', 0, N_("Search devices by a partition label."), + 0, 0}, + {"part-uuid", 'U', 0, N_("Search devices by a partition UUID."), + 0, 0}, + {"disk-uuid", 'U', 0, N_("Search devices by a disk UUID."), + 0, 0}, {"set", 's', GRUB_ARG_OPTION_OPTIONAL, N_("Set a variable to the first device found."), N_("VARNAME"), ARG_TYPE_STRING}, @@ -71,6 +77,9 @@ enum options SEARCH_FILE, SEARCH_LABEL, SEARCH_FS_UUID, + SEARCH_PART_LABEL, + SEARCH_PART_UUID, + SEARCH_DISK_UUID, SEARCH_SET, SEARCH_NO_FLOPPY, SEARCH_HINT, @@ -186,6 +195,15 @@ grub_cmd_search (grub_extcmd_context_t ctxt, int argc, char **args) else if (state[SEARCH_FS_UUID].set) grub_search_fs_uuid (id, var, state[SEARCH_NO_FLOPPY].set, hints, nhints); + else if (state[SEARCH_PART_LABEL].set) + grub_search_part_label (id, var, state[SEARCH_NO_FLOPPY].set, + hints, nhints); + else if (state[SEARCH_PART_UUID].set) + grub_search_part_uuid (id, var, state[SEARCH_NO_FLOPPY].set, + hints, nhints); + else if (state[SEARCH_DISK_UUID].set) + grub_search_disk_uuid (id, var, state[SEARCH_NO_FLOPPY].set, + hints, nhints); else if (state[SEARCH_FILE].set) grub_search_fs_file (id, var, state[SEARCH_NO_FLOPPY].set, hints, nhints); diff --git a/grub-core/commands/smbios.c b/grub-core/commands/smbios.c new file mode 100644 index 000000000..82186319a --- /dev/null +++ b/grub-core/commands/smbios.c @@ -0,0 +1,372 @@ +/* smbios.c - retrieve smbios information. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2013,2014,2015 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + + +/* Locate the SMBIOS entry point structure depending on the hardware. */ +struct grub_smbios_eps * +grub_smbios_get_eps (void) +{ + static struct grub_smbios_eps *eps = NULL; + if (eps != NULL) + return eps; + eps = grub_machine_smbios_get_eps (); + return eps; +} + +/* Locate the SMBIOS3 entry point structure depending on the hardware. */ +struct grub_smbios_eps3 * +grub_smbios_get_eps3 (void) +{ + static struct grub_smbios_eps3 *eps = NULL; + if (eps != NULL) + return eps; + eps = grub_machine_smbios_get_eps3 (); + return eps; +} + +/* Abstract useful values found in either the SMBIOS3 or SMBIOS EPS. */ +static struct { + grub_addr_t start; + grub_addr_t end; + grub_uint16_t structures; +} table_desc = {0, 0, 0}; + + +/* + * These functions convert values from the various SMBIOS structure field types + * into a string formatted to be returned to the user. They expect that the + * structure and offset were already validated. The given buffer stores the + * newly formatted string if needed. When the requested data is successfully + * retrieved and formatted, the pointer to the string is returned; otherwise, + * NULL is returned on failure. + */ + +static const char * +grub_smbios_format_byte (char *buffer, grub_size_t size, + const grub_uint8_t *structure, grub_uint8_t offset) +{ + grub_snprintf (buffer, size, "%u", structure[offset]); + return (const char *)buffer; +} + +static const char * +grub_smbios_format_word (char *buffer, grub_size_t size, + const grub_uint8_t *structure, grub_uint8_t offset) +{ + grub_uint16_t value = grub_get_unaligned16 (structure + offset); + grub_snprintf (buffer, size, "%u", value); + return (const char *)buffer; +} + +static const char * +grub_smbios_format_dword (char *buffer, grub_size_t size, + const grub_uint8_t *structure, grub_uint8_t offset) +{ + grub_uint32_t value = grub_get_unaligned32 (structure + offset); + grub_snprintf (buffer, size, "%" PRIuGRUB_UINT32_T, value); + return (const char *)buffer; +} + +static const char * +grub_smbios_format_qword (char *buffer, grub_size_t size, + const grub_uint8_t *structure, grub_uint8_t offset) +{ + grub_uint64_t value = grub_get_unaligned64 (structure + offset); + grub_snprintf (buffer, size, "%" PRIuGRUB_UINT64_T, value); + return (const char *)buffer; +} + +/* The matching string pointer is returned directly to avoid extra copying. */ +static const char * +grub_smbios_get_string (char *buffer __attribute__ ((unused)), + grub_size_t size __attribute__ ((unused)), + const grub_uint8_t *structure, grub_uint8_t offset) +{ + const grub_uint8_t *ptr = structure + structure[1]; + const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end; + const grub_uint8_t referenced_string_number = structure[offset]; + grub_uint8_t i; + + /* A string referenced with zero is interpreted as unset. */ + if (referenced_string_number == 0) + return NULL; + + /* Search the string set. */ + for (i = 1; *ptr != 0 && ptr < table_end; i++) + if (i == referenced_string_number) + { + const char *str = (const char *)ptr; + while (*ptr++ != 0) + if (ptr >= table_end) + return NULL; /* The string isn't terminated. */ + return str; + } + else + while (*ptr++ != 0 && ptr < table_end); + + /* The string number is greater than the number of strings in the set. */ + return NULL; +} + +static const char * +grub_smbios_format_uuid (char *buffer, grub_size_t size, + const grub_uint8_t *structure, grub_uint8_t offset) +{ + const grub_uint8_t *f = structure + offset; /* little-endian fields */ + const grub_uint8_t *g = f + 8; /* byte-by-byte fields */ + grub_snprintf (buffer, size, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x-%02x%02x%02x%02x%02x%02x", + f[3], f[2], f[1], f[0], f[5], f[4], f[7], f[6], + g[0], g[1], g[2], g[3], g[4], g[5], g[6], g[7]); + return (const char *)buffer; +} + + +/* List the field formatting functions and the number of bytes they need. */ +#define MAXIMUM_FORMAT_LENGTH (sizeof ("ffffffff-ffff-ffff-ffff-ffffffffffff")) +static const struct { + const char *(*format) (char *buffer, grub_size_t size, + const grub_uint8_t *structure, grub_uint8_t offset); + grub_uint8_t field_length; +} field_extractors[] = { + {grub_smbios_format_byte, 1}, + {grub_smbios_format_word, 2}, + {grub_smbios_format_dword, 4}, + {grub_smbios_format_qword, 8}, + {grub_smbios_get_string, 1}, + {grub_smbios_format_uuid, 16} +}; + +/* List command options, with structure field getters ordered as above. */ +#define FIRST_GETTER_OPT (3) +#define SETTER_OPT (FIRST_GETTER_OPT + ARRAY_SIZE(field_extractors)) +static const struct grub_arg_option options[] = { + {"type", 't', 0, N_("Match entries with the given type."), + N_("type"), ARG_TYPE_INT}, + {"handle", 'h', 0, N_("Match entries with the given handle."), + N_("handle"), ARG_TYPE_INT}, + {"match", 'm', 0, N_("Select a structure when several match."), + N_("match"), ARG_TYPE_INT}, + {"get-byte", 'b', 0, N_("Get the byte's value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-word", 'w', 0, N_("Get two bytes' value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-dword", 'd', 0, N_("Get four bytes' value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-qword", 'q', 0, N_("Get eight bytes' value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-string", 's', 0, N_("Get the string specified at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"get-uuid", 'u', 0, N_("Get the UUID's value at the given offset."), + N_("offset"), ARG_TYPE_INT}, + {"set", '\0', 0, N_("Store the value in the given variable name."), + N_("variable"), ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} +}; + + +/* + * Return a matching SMBIOS structure. + * + * This method can use up to three criteria for selecting a structure: + * - The "type" field (use -1 to ignore) + * - The "handle" field (use -1 to ignore) + * - Which to return if several match (use 0 to ignore) + * + * The return value is a pointer to the first matching structure. If no + * structures match the given parameters, NULL is returned. + */ +static const grub_uint8_t * +grub_smbios_match_structure (const grub_int16_t type, + const grub_int32_t handle, + const grub_uint16_t match) +{ + const grub_uint8_t *ptr = (const grub_uint8_t *)table_desc.start; + const grub_uint8_t *table_end = (const grub_uint8_t *)table_desc.end; + grub_uint16_t structures = table_desc.structures; + grub_uint16_t structure_count = 0; + grub_uint16_t matches = 0; + + while (ptr < table_end + && ptr[1] >= 4 /* Valid structures include the 4-byte header. */ + && (structure_count++ < structures || structures == 0)) + { + grub_uint16_t structure_handle = grub_get_unaligned16 (ptr + 2); + grub_uint8_t structure_type = ptr[0]; + + if ((handle < 0 || handle == structure_handle) + && (type < 0 || type == structure_type) + && (match == 0 || match == ++matches)) + return ptr; + + else + { + ptr += ptr[1]; + while ((*ptr++ != 0 || *ptr++ != 0) && ptr < table_end); + } + + if (structure_type == GRUB_SMBIOS_TYPE_END_OF_TABLE) + break; + } + + return NULL; +} + + +static grub_err_t +grub_cmd_smbios (grub_extcmd_context_t ctxt, + int argc __attribute__ ((unused)), + char **argv __attribute__ ((unused))) +{ + struct grub_arg_list *state = ctxt->state; + + grub_int16_t type = -1; + grub_int32_t handle = -1; + grub_uint16_t match = 0; + grub_uint8_t offset = 0; + + const grub_uint8_t *structure; + const char *value; + char buffer[MAXIMUM_FORMAT_LENGTH]; + grub_int32_t option; + grub_int8_t field_type = -1; + grub_uint8_t i; + + if (table_desc.start == 0) + return grub_error (GRUB_ERR_IO, + N_("the SMBIOS entry point structure was not found")); + + /* Read the given filtering options. */ + if (state[0].set) + { + option = grub_strtol (state[0].arg, NULL, 0); + if (option < 0 || option > 255) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("the type must be between 0 and 255")); + type = (grub_int16_t)option; + } + if (state[1].set) + { + option = grub_strtol (state[1].arg, NULL, 0); + if (option < 0 || option > 65535) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("the handle must be between 0 and 65535")); + handle = (grub_int32_t)option; + } + if (state[2].set) + { + option = grub_strtol (state[2].arg, NULL, 0); + if (option <= 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("the match must be a positive integer")); + match = (grub_uint16_t)option; + } + + /* Determine the data type of the structure field to retrieve. */ + for (i = 0; i < ARRAY_SIZE(field_extractors); i++) + if (state[FIRST_GETTER_OPT + i].set) + { + if (field_type >= 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("only one --get option is usable at a time")); + field_type = i; + } + + /* Require a choice of a structure field to return. */ + if (field_type < 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("one of the --get options is required")); + + /* Locate a matching SMBIOS structure. */ + structure = grub_smbios_match_structure (type, handle, match); + if (structure == NULL) + return grub_error (GRUB_ERR_IO, + N_("no structure matched the given options")); + + /* Ensure the requested byte offset is inside the structure. */ + option = grub_strtol (state[FIRST_GETTER_OPT + field_type].arg, NULL, 0); + if (option < 0 || option >= structure[1]) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the given offset is outside the structure")); + + /* Ensure the requested data type at the offset is inside the structure. */ + offset = (grub_uint8_t)option; + if (offset + field_extractors[field_type].field_length > structure[1]) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("the field ends outside the structure")); + + /* Format the requested structure field into a readable string. */ + value = field_extractors[field_type].format (buffer, sizeof (buffer), + structure, offset); + if (value == NULL) + return grub_error (GRUB_ERR_IO, + N_("failed to retrieve the structure field")); + + /* Store or print the formatted value. */ + if (state[SETTER_OPT].set) + grub_env_set (state[SETTER_OPT].arg, value); + else + grub_printf ("%s\n", value); + + return GRUB_ERR_NONE; +} + + +static grub_extcmd_t cmd; + +GRUB_MOD_INIT(smbios) +{ + struct grub_smbios_eps3 *eps3; + struct grub_smbios_eps *eps; + + if ((eps3 = grub_smbios_get_eps3 ())) + { + table_desc.start = (grub_addr_t)eps3->table_address; + table_desc.end = table_desc.start + eps3->maximum_table_length; + table_desc.structures = 0; /* SMBIOS3 drops the structure count. */ + } + else if ((eps = grub_smbios_get_eps ())) + { + table_desc.start = (grub_addr_t)eps->intermediate.table_address; + table_desc.end = table_desc.start + eps->intermediate.table_length; + table_desc.structures = eps->intermediate.structures; + } + + cmd = grub_register_extcmd ("smbios", grub_cmd_smbios, 0, + N_("[-t type] [-h handle] [-m match] " + "(-b|-w|-d|-q|-s|-u) offset " + "[--set variable]"), + N_("Retrieve SMBIOS information."), options); +} + +GRUB_MOD_FINI(smbios) +{ + grub_unregister_extcmd (cmd); +} diff --git a/grub-core/disk/i386/pc/biosdisk.c b/grub-core/disk/i386/pc/biosdisk.c index 8ca250c77..3d3033547 100644 --- a/grub-core/disk/i386/pc/biosdisk.c +++ b/grub-core/disk/i386/pc/biosdisk.c @@ -278,10 +278,14 @@ grub_biosdisk_call_hook (grub_disk_dev_iterate_hook_t hook, void *hook_data, char name[10]; if (cd_drive && drive == cd_drive) - return hook ("cd", hook_data); + { + grub_dprintf ("biosdisk", "iterating cd\n"); + return hook ("cd", hook_data); + } grub_snprintf (name, sizeof (name), (drive & 0x80) ? "hd%d" : "fd%d", drive & (~0x80)); + grub_dprintf ("biosdisk", "iterating %s\n", name); return hook (name, hook_data); } @@ -301,7 +305,7 @@ grub_biosdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, if (grub_biosdisk_rw_standard (0x02, drive, 0, 0, 1, 1, GRUB_MEMORY_MACHINE_SCRATCH_SEG) != 0) { - grub_dprintf ("disk", "Read error when probing drive 0x%2x\n", drive); + grub_dprintf ("biosdisk", "Read error when probing drive 0x%2x\n", drive); break; } @@ -336,6 +340,8 @@ grub_biosdisk_open (const char *name, grub_disk_t disk) int drive; struct grub_biosdisk_data *data; + grub_dprintf ("biosdisk", "opening %s\n", name); + drive = grub_biosdisk_get_drive (name); if (drive < 0) return grub_errno; @@ -393,6 +399,11 @@ grub_biosdisk_open (const char *name, grub_disk_t disk) (1 << disk->log_sector_size) < drp->bytes_per_sector; disk->log_sector_size++); } + + grub_dprintf ("biosdisk", + "LBA total = 0x%llx block size = 0x%lx\n", + (unsigned long long) total_sectors, + 1L << disk->log_sector_size); } } } @@ -428,6 +439,9 @@ grub_biosdisk_open (const char *name, grub_disk_t disk) if (! total_sectors) total_sectors = ((grub_uint64_t) data->cylinders) * data->heads * data->sectors; + + grub_dprintf ("biosdisk", "C/H/S %lu/%lu/%lu\n", + data->cylinders, data->heads, data->sectors); } disk->total_sectors = total_sectors; @@ -440,12 +454,15 @@ grub_biosdisk_open (const char *name, grub_disk_t disk) disk->data = data; + grub_dprintf ("biosdisk", "opening %s succeeded\n", name); + return GRUB_ERR_NONE; } static void grub_biosdisk_close (grub_disk_t disk) { + grub_dprintf ("biosdisk", "closing %s\n", disk->name); grub_free (disk->data); } @@ -577,6 +594,9 @@ static grub_err_t grub_biosdisk_read (grub_disk_t disk, grub_disk_addr_t sector, grub_size_t size, char *buf) { + grub_dprintf ("biosdisk", "reading 0x%lx sectors at 0x%llx from %s\n", + (unsigned long) size, (unsigned long long) sector, disk->name); + while (size) { grub_size_t len; @@ -607,6 +627,9 @@ grub_biosdisk_write (grub_disk_t disk, grub_disk_addr_t sector, { struct grub_biosdisk_data *data = disk->data; + grub_dprintf ("biosdisk", "writing 0x%lx sectors at 0x%llx to %s\n", + (unsigned long) size, (unsigned long long) sector, disk->name); + if (data->flags & GRUB_BIOSDISK_FLAG_CDROM) return grub_error (GRUB_ERR_IO, N_("cannot write to CD-ROM")); diff --git a/grub-core/efiemu/i386/pc/cfgtables.c b/grub-core/efiemu/i386/pc/cfgtables.c index 492c07c46..1d8833115 100644 --- a/grub-core/efiemu/i386/pc/cfgtables.c +++ b/grub-core/efiemu/i386/pc/cfgtables.c @@ -22,18 +22,22 @@ #include #include #include +#include grub_err_t grub_machine_efiemu_init_tables (void) { - grub_uint8_t *ptr; void *table; grub_err_t err; grub_efi_guid_t smbios = GRUB_EFI_SMBIOS_TABLE_GUID; + grub_efi_guid_t smbios3 = GRUB_EFI_SMBIOS3_TABLE_GUID; grub_efi_guid_t acpi20 = GRUB_EFI_ACPI_20_TABLE_GUID; grub_efi_guid_t acpi = GRUB_EFI_ACPI_TABLE_GUID; err = grub_efiemu_unregister_configuration_table (smbios); + if (err) + return err; + err = grub_efiemu_unregister_configuration_table (smbios3); if (err) return err; err = grub_efiemu_unregister_configuration_table (acpi); @@ -43,6 +47,20 @@ grub_machine_efiemu_init_tables (void) if (err) return err; + table = grub_smbios_get_eps (); + if (table) + { + err = grub_efiemu_register_configuration_table (smbios, 0, 0, table); + if (err) + return err; + } + table = grub_smbios_get_eps3 (); + if (table) + { + err = grub_efiemu_register_configuration_table (smbios3, 0, 0, table); + if (err) + return err; + } table = grub_acpi_get_rsdpv1 (); if (table) { @@ -58,19 +76,5 @@ grub_machine_efiemu_init_tables (void) return err; } - for (ptr = (grub_uint8_t *) 0xf0000; ptr < (grub_uint8_t *) 0x100000; - ptr += 16) - if (grub_memcmp (ptr, "_SM_", 4) == 0 - && grub_byte_checksum (ptr, *(ptr + 5)) == 0) - break; - - if (ptr < (grub_uint8_t *) 0x100000) - { - grub_dprintf ("efiemu", "Registering SMBIOS\n"); - err = grub_efiemu_register_configuration_table (smbios, 0, 0, ptr); - if (err) - return err; - } - return GRUB_ERR_NONE; } diff --git a/grub-core/kern/dl.c b/grub-core/kern/dl.c index 48eb5e7b6..24d506512 100644 --- a/grub-core/kern/dl.c +++ b/grub-core/kern/dl.c @@ -32,12 +32,21 @@ #include #include #include +#include /* Platforms where modules are in a readonly area of memory. */ #if defined(GRUB_MACHINE_QEMU) #define GRUB_MODULES_MACHINE_READONLY #endif +#ifdef GRUB_MACHINE_EMU +#include +#endif + +#ifdef GRUB_MACHINE_EFI +#include +#endif + #pragma GCC diagnostic ignored "-Wcast-align" @@ -686,6 +695,15 @@ grub_dl_load_file (const char *filename) void *core = 0; grub_dl_t mod = 0; +#ifdef GRUB_MACHINE_EFI + if (grub_efi_secure_boot ()) + { + grub_error (GRUB_ERR_ACCESS_DENIED, + "Secure Boot forbids loading module from %s", filename); + return 0; + } +#endif + grub_boot_time ("Loading module %s", filename); file = grub_file_open (filename, GRUB_FILE_TYPE_GRUB_MODULE); @@ -712,6 +730,9 @@ grub_dl_load_file (const char *filename) opens of the same device. */ grub_file_close (file); + grub_tpm_measure(core, size, GRUB_BINARY_PCR, filename); + grub_print_error(); + mod = grub_dl_load_core (core, size); grub_free (core); if (! mod) diff --git a/grub-core/kern/efi/efi.c b/grub-core/kern/efi/efi.c index 6e1ceb905..a0faa40ec 100644 --- a/grub-core/kern/efi/efi.c +++ b/grub-core/kern/efi/efi.c @@ -273,6 +273,34 @@ grub_efi_get_variable (const char *var, const grub_efi_guid_t *guid, return NULL; } +grub_efi_boolean_t +grub_efi_secure_boot (void) +{ + grub_efi_guid_t efi_var_guid = GRUB_EFI_GLOBAL_VARIABLE_GUID; + grub_size_t datasize; + char *secure_boot = NULL; + char *setup_mode = NULL; + grub_efi_boolean_t ret = 0; + + secure_boot = grub_efi_get_variable("SecureBoot", &efi_var_guid, &datasize); + + if (datasize != 1 || !secure_boot) + goto out; + + setup_mode = grub_efi_get_variable("SetupMode", &efi_var_guid, &datasize); + + if (datasize != 1 || !setup_mode) + goto out; + + if (*secure_boot && !*setup_mode) + ret = 1; + + out: + grub_free (secure_boot); + grub_free (setup_mode); + return ret; +} + #pragma GCC diagnostic ignored "-Wcast-align" /* Search the mods section from the PE32/PE32+ image. This code uses diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c index b02fab1b1..671b1bf4d 100644 --- a/grub-core/kern/efi/mm.c +++ b/grub-core/kern/efi/mm.c @@ -49,6 +49,38 @@ static grub_efi_uintn_t finish_desc_size; static grub_efi_uint32_t finish_desc_version; int grub_efi_is_finished = 0; +/* Allocate pages below a specified address */ +void * +grub_efi_allocate_pages_max (grub_efi_physical_address_t max, + grub_efi_uintn_t pages) +{ + grub_efi_status_t status; + grub_efi_boot_services_t *b; + grub_efi_physical_address_t address = max; + + if (max > 0xffffffff) + return 0; + + b = grub_efi_system_table->boot_services; + status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_MAX_ADDRESS, GRUB_EFI_LOADER_DATA, pages, &address); + + if (status != GRUB_EFI_SUCCESS) + return 0; + + if (address == 0) + { + /* Uggh, the address 0 was allocated... This is too annoying, + so reallocate another one. */ + address = max; + status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_MAX_ADDRESS, GRUB_EFI_LOADER_DATA, pages, &address); + grub_efi_free_pages (0, pages); + if (status != GRUB_EFI_SUCCESS) + return 0; + } + + return (void *) ((grub_addr_t) address); +} + /* * We need to roll back EFI allocations on exit. Remember allocations that * we'll free on exit. diff --git a/grub-core/kern/efi/tpm.c b/grub-core/kern/efi/tpm.c new file mode 100644 index 000000000..532e534e4 --- /dev/null +++ b/grub-core/kern/efi/tpm.c @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +static grub_efi_guid_t tpm_guid = EFI_TPM_GUID; +static grub_efi_guid_t tpm2_guid = EFI_TPM2_GUID; + +static grub_efi_boolean_t grub_tpm_present(grub_efi_tpm_protocol_t *tpm) +{ + grub_efi_status_t status; + TCG_EFI_BOOT_SERVICE_CAPABILITY caps; + grub_uint32_t flags; + grub_efi_physical_address_t eventlog, lastevent; + + caps.Size = (grub_uint8_t)sizeof(caps); + + status = efi_call_5(tpm->status_check, tpm, &caps, &flags, &eventlog, + &lastevent); + + if (status != GRUB_EFI_SUCCESS || caps.TPMDeactivatedFlag + || !caps.TPMPresentFlag) + return 0; + + return 1; +} + +static grub_efi_boolean_t grub_tpm2_present(grub_efi_tpm2_protocol_t *tpm) +{ + grub_efi_status_t status; + EFI_TCG2_BOOT_SERVICE_CAPABILITY caps; + + caps.Size = (grub_uint8_t)sizeof(caps); + + status = efi_call_2(tpm->get_capability, tpm, &caps); + + if (status != GRUB_EFI_SUCCESS || !caps.TPMPresentFlag) + return 0; + + return 1; +} + +static grub_efi_boolean_t grub_tpm_handle_find(grub_efi_handle_t *tpm_handle, + grub_efi_uint8_t *protocol_version) +{ + grub_efi_handle_t *handles; + grub_efi_uintn_t num_handles; + + handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &tpm_guid, NULL, + &num_handles); + if (handles && num_handles > 0) { + *tpm_handle = handles[0]; + *protocol_version = 1; + return 1; + } + + handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &tpm2_guid, NULL, + &num_handles); + if (handles && num_handles > 0) { + *tpm_handle = handles[0]; + *protocol_version = 2; + return 1; + } + + return 0; +} + +static grub_err_t +grub_tpm1_execute(grub_efi_handle_t tpm_handle, + PassThroughToTPM_InputParamBlock *inbuf, + PassThroughToTPM_OutputParamBlock *outbuf) +{ + grub_efi_status_t status; + grub_efi_tpm_protocol_t *tpm; + grub_uint32_t inhdrsize = sizeof(*inbuf) - sizeof(inbuf->TPMOperandIn); + grub_uint32_t outhdrsize = sizeof(*outbuf) - sizeof(outbuf->TPMOperandOut); + + tpm = grub_efi_open_protocol (tpm_handle, &tpm_guid, + GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (!grub_tpm_present(tpm)) + return 0; + + /* UEFI TPM protocol takes the raw operand block, no param block header */ + status = efi_call_5 (tpm->pass_through_to_tpm, tpm, + inbuf->IPBLength - inhdrsize, inbuf->TPMOperandIn, + outbuf->OPBLength - outhdrsize, outbuf->TPMOperandOut); + + switch (status) { + case GRUB_EFI_SUCCESS: + return 0; + case GRUB_EFI_DEVICE_ERROR: + return grub_error (GRUB_ERR_IO, N_("Command failed")); + case GRUB_EFI_INVALID_PARAMETER: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Invalid parameter")); + case GRUB_EFI_BUFFER_TOO_SMALL: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Output buffer too small")); + case GRUB_EFI_NOT_FOUND: + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("TPM unavailable")); + default: + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("Unknown TPM error")); + } +} + +static grub_err_t +grub_tpm2_execute(grub_efi_handle_t tpm_handle, + PassThroughToTPM_InputParamBlock *inbuf, + PassThroughToTPM_OutputParamBlock *outbuf) +{ + grub_efi_status_t status; + grub_efi_tpm2_protocol_t *tpm; + grub_uint32_t inhdrsize = sizeof(*inbuf) - sizeof(inbuf->TPMOperandIn); + grub_uint32_t outhdrsize = sizeof(*outbuf) - sizeof(outbuf->TPMOperandOut); + + tpm = grub_efi_open_protocol (tpm_handle, &tpm2_guid, + GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (!grub_tpm2_present(tpm)) + return 0; + + /* UEFI TPM protocol takes the raw operand block, no param block header */ + status = efi_call_5 (tpm->submit_command, tpm, + inbuf->IPBLength - inhdrsize, inbuf->TPMOperandIn, + outbuf->OPBLength - outhdrsize, outbuf->TPMOperandOut); + + switch (status) { + case GRUB_EFI_SUCCESS: + return 0; + case GRUB_EFI_DEVICE_ERROR: + return grub_error (GRUB_ERR_IO, N_("Command failed")); + case GRUB_EFI_INVALID_PARAMETER: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Invalid parameter")); + case GRUB_EFI_BUFFER_TOO_SMALL: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Output buffer too small")); + case GRUB_EFI_NOT_FOUND: + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("TPM unavailable")); + default: + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("Unknown TPM error")); + } +} + +grub_err_t +grub_tpm_execute(PassThroughToTPM_InputParamBlock *inbuf, + PassThroughToTPM_OutputParamBlock *outbuf) +{ + grub_efi_handle_t tpm_handle; + grub_uint8_t protocol_version; + + /* It's not a hard failure for there to be no TPM */ + if (!grub_tpm_handle_find(&tpm_handle, &protocol_version)) + return 0; + + if (protocol_version == 1) { + return grub_tpm1_execute(tpm_handle, inbuf, outbuf); + } else { + return grub_tpm2_execute(tpm_handle, inbuf, outbuf); + } +} + +static grub_err_t +grub_tpm1_log_event(grub_efi_handle_t tpm_handle, unsigned char *buf, + grub_size_t size, grub_uint8_t pcr, + const char *description) +{ + TCG_PCR_EVENT *event; + grub_efi_status_t status; + grub_efi_tpm_protocol_t *tpm; + grub_efi_physical_address_t lastevent; + grub_uint32_t algorithm; + grub_uint32_t eventnum = 0; + + tpm = grub_efi_open_protocol (tpm_handle, &tpm_guid, + GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (!grub_tpm_present(tpm)) + return 0; + + event = grub_zalloc(sizeof (TCG_PCR_EVENT) + grub_strlen(description) + 1); + if (!event) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + N_("cannot allocate TPM event buffer")); + + event->PCRIndex = pcr; + event->EventType = EV_IPL; + event->EventSize = grub_strlen(description) + 1; + grub_memcpy(event->Event, description, event->EventSize); + + algorithm = TCG_ALG_SHA; + status = efi_call_7 (tpm->log_extend_event, tpm, (grub_efi_physical_address_t)buf, (grub_uint64_t) size, + algorithm, event, &eventnum, &lastevent); + + switch (status) { + case GRUB_EFI_SUCCESS: + return 0; + case GRUB_EFI_DEVICE_ERROR: + return grub_error (GRUB_ERR_IO, N_("Command failed")); + case GRUB_EFI_INVALID_PARAMETER: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Invalid parameter")); + case GRUB_EFI_BUFFER_TOO_SMALL: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Output buffer too small")); + case GRUB_EFI_NOT_FOUND: + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("TPM unavailable")); + default: + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("Unknown TPM error")); + } +} + +static grub_err_t +grub_tpm2_log_event(grub_efi_handle_t tpm_handle, unsigned char *buf, + grub_size_t size, grub_uint8_t pcr, + const char *description) +{ + EFI_TCG2_EVENT *event; + grub_efi_status_t status; + grub_efi_tpm2_protocol_t *tpm; + + tpm = grub_efi_open_protocol (tpm_handle, &tpm2_guid, + GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (!grub_tpm2_present(tpm)) + return 0; + + event = grub_zalloc(sizeof (EFI_TCG2_EVENT) + grub_strlen(description) + 1); + if (!event) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + N_("cannot allocate TPM event buffer")); + + event->Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER); + event->Header.HeaderVersion = 1; + event->Header.PCRIndex = pcr; + event->Header.EventType = EV_IPL; + event->Size = sizeof(*event) - sizeof(event->Event) + grub_strlen(description) + 1; + grub_memcpy(event->Event, description, grub_strlen(description) + 1); + + status = efi_call_5 (tpm->hash_log_extend_event, tpm, 0, (grub_efi_physical_address_t)buf, + (grub_uint64_t) size, event); + + switch (status) { + case GRUB_EFI_SUCCESS: + return 0; + case GRUB_EFI_DEVICE_ERROR: + return grub_error (GRUB_ERR_IO, N_("Command failed")); + case GRUB_EFI_INVALID_PARAMETER: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Invalid parameter")); + case GRUB_EFI_BUFFER_TOO_SMALL: + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("Output buffer too small")); + case GRUB_EFI_NOT_FOUND: + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("TPM unavailable")); + default: + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, N_("Unknown TPM error")); + } +} + +grub_err_t +grub_tpm_log_event(unsigned char *buf, grub_size_t size, grub_uint8_t pcr, + const char *description) +{ + grub_efi_handle_t tpm_handle; + grub_efi_uint8_t protocol_version; + + if (!grub_tpm_handle_find(&tpm_handle, &protocol_version)) + return 0; + + if (protocol_version == 1) { + return grub_tpm1_log_event(tpm_handle, buf, size, pcr, description); + } else { + return grub_tpm2_log_event(tpm_handle, buf, size, pcr, description); + } +} diff --git a/grub-core/kern/i386/pc/tpm.c b/grub-core/kern/i386/pc/tpm.c new file mode 100644 index 000000000..f6f264aff --- /dev/null +++ b/grub-core/kern/i386/pc/tpm.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include + +#define TCPA_MAGIC 0x41504354 + +static int tpm_presence = -1; + +int tpm_present(void); + +int tpm_present(void) +{ + struct grub_bios_int_registers regs; + + if (tpm_presence != -1) + return tpm_presence; + + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + regs.eax = 0xbb00; + regs.ebx = TCPA_MAGIC; + grub_bios_interrupt (0x1a, ®s); + + if (regs.eax == 0) + tpm_presence = 1; + else + tpm_presence = 0; + + return tpm_presence; +} + +grub_err_t +grub_tpm_execute(PassThroughToTPM_InputParamBlock *inbuf, + PassThroughToTPM_OutputParamBlock *outbuf) +{ + struct grub_bios_int_registers regs; + grub_addr_t inaddr, outaddr; + + if (!tpm_present()) + return 0; + + inaddr = (grub_addr_t) inbuf; + outaddr = (grub_addr_t) outbuf; + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + regs.eax = 0xbb02; + regs.ebx = TCPA_MAGIC; + regs.ecx = 0; + regs.edx = 0; + regs.es = (inaddr & 0xffff0000) >> 4; + regs.edi = inaddr & 0xffff; + regs.ds = outaddr >> 4; + regs.esi = outaddr & 0xf; + + grub_bios_interrupt (0x1a, ®s); + + if (regs.eax) + { + tpm_presence = 0; + return grub_error (GRUB_ERR_IO, N_("TPM error %x, disabling TPM"), regs.eax); + } + + return 0; +} + +typedef struct { + grub_uint32_t pcrindex; + grub_uint32_t eventtype; + grub_uint8_t digest[20]; + grub_uint32_t eventdatasize; + grub_uint8_t event[0]; +} GRUB_PACKED Event; + +typedef struct { + grub_uint16_t ipblength; + grub_uint16_t reserved; + grub_uint32_t hashdataptr; + grub_uint32_t hashdatalen; + grub_uint32_t pcr; + grub_uint32_t reserved2; + grub_uint32_t logdataptr; + grub_uint32_t logdatalen; +} GRUB_PACKED EventIncoming; + +typedef struct { + grub_uint16_t opblength; + grub_uint16_t reserved; + grub_uint32_t eventnum; + grub_uint8_t hashvalue[20]; +} GRUB_PACKED EventOutgoing; + +grub_err_t +grub_tpm_log_event(unsigned char *buf, grub_size_t size, grub_uint8_t pcr, + const char *description) +{ + struct grub_bios_int_registers regs; + EventIncoming incoming; + EventOutgoing outgoing; + Event *event; + grub_uint32_t datalength; + + if (!tpm_present()) + return 0; + + datalength = grub_strlen(description); + event = grub_zalloc(datalength + sizeof(Event)); + if (!event) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + N_("cannot allocate TPM event buffer")); + + event->pcrindex = pcr; + event->eventtype = 0x0d; + event->eventdatasize = grub_strlen(description); + grub_memcpy(event->event, description, datalength); + + incoming.ipblength = sizeof(incoming); + incoming.hashdataptr = (grub_uint32_t)buf; + incoming.hashdatalen = size; + incoming.pcr = pcr; + incoming.logdataptr = (grub_uint32_t)event; + incoming.logdatalen = datalength + sizeof(Event); + + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + regs.eax = 0xbb01; + regs.ebx = TCPA_MAGIC; + regs.ecx = 0; + regs.edx = 0; + regs.es = (((grub_addr_t) &incoming) & 0xffff0000) >> 4; + regs.edi = ((grub_addr_t) &incoming) & 0xffff; + regs.ds = (((grub_addr_t) &outgoing) & 0xffff0000) >> 4; + regs.esi = ((grub_addr_t) &outgoing) & 0xffff; + + grub_bios_interrupt (0x1a, ®s); + + grub_free(event); + + if (regs.eax) + { + tpm_presence = 0; + return grub_error (GRUB_ERR_IO, N_("TPM error %x, disabling TPM"), regs.eax); + } + + return 0; +} diff --git a/grub-core/kern/main.c b/grub-core/kern/main.c index 9cad0c448..682d598f1 100644 --- a/grub-core/kern/main.c +++ b/grub-core/kern/main.c @@ -131,6 +131,9 @@ grub_set_prefix_and_root (void) { char *cmdpath; + grub_env_set ("cmddevice", fwdevice); + grub_env_export ("cmddevice"); + cmdpath = grub_xasprintf ("(%s)%s", fwdevice, fwpath ? : ""); if (cmdpath) { diff --git a/grub-core/kern/tpm.c b/grub-core/kern/tpm.c new file mode 100644 index 000000000..9841d8d38 --- /dev/null +++ b/grub-core/kern/tpm.c @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include +#include + +grub_err_t +grub_tpm_measure (unsigned char *buf, grub_size_t size, grub_uint8_t pcr, + const char *description) +{ + return grub_tpm_log_event (buf, size, pcr, description); +} + diff --git a/grub-core/lib/cmdline.c b/grub-core/lib/cmdline.c index ed0b149dc..ecfbe3409 100644 --- a/grub-core/lib/cmdline.c +++ b/grub-core/lib/cmdline.c @@ -19,6 +19,7 @@ #include #include +#include static unsigned int check_arg (char *c, int *has_space) { diff --git a/grub-core/lib/gpt.c b/grub-core/lib/gpt.c new file mode 100644 index 000000000..80bca2e4e --- /dev/null +++ b/grub-core/lib/gpt.c @@ -0,0 +1,777 @@ +/* gpt.c - Read/Verify/Write GUID Partition Tables (GPT). */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2005,2006,2007,2008 Free Software Foundation, Inc. + * Copyright (C) 2014 CoreOS, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_uint8_t grub_gpt_magic[] = GRUB_GPT_HEADER_MAGIC; + +static grub_err_t +grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt, + struct grub_gpt_header *header, + void **ret_entries, + grub_size_t *ret_entries_size); + +char * +grub_gpt_guid_to_str (grub_gpt_part_guid_t *guid) +{ + return grub_xasprintf ("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + grub_le_to_cpu32 (guid->data1), + grub_le_to_cpu16 (guid->data2), + grub_le_to_cpu16 (guid->data3), + guid->data4[0], guid->data4[1], + guid->data4[2], guid->data4[3], + guid->data4[4], guid->data4[5], + guid->data4[6], guid->data4[7]); +} + +static grub_err_t +grub_gpt_device_partentry (grub_device_t device, + struct grub_gpt_partentry *entry) +{ + grub_disk_t disk = device->disk; + grub_partition_t p; + grub_err_t err; + + if (!disk || !disk->partition) + return grub_error (GRUB_ERR_BUG, "not a partition"); + + if (grub_strcmp (disk->partition->partmap->name, "gpt")) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "not a GPT partition"); + + p = disk->partition; + disk->partition = p->parent; + err = grub_disk_read (disk, p->offset, p->index, sizeof (*entry), entry); + disk->partition = p; + + return err; +} + +grub_err_t +grub_gpt_part_label (grub_device_t device, char **label) +{ + struct grub_gpt_partentry entry; + const grub_size_t name_len = ARRAY_SIZE (entry.name); + const grub_size_t label_len = name_len * GRUB_MAX_UTF8_PER_UTF16 + 1; + grub_size_t i; + grub_uint8_t *end; + + if (grub_gpt_device_partentry (device, &entry)) + return grub_errno; + + *label = grub_malloc (label_len); + if (!*label) + return grub_errno; + + for (i = 0; i < name_len; i++) + entry.name[i] = grub_le_to_cpu16 (entry.name[i]); + + end = grub_utf16_to_utf8 ((grub_uint8_t *) *label, entry.name, name_len); + *end = '\0'; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_gpt_part_uuid (grub_device_t device, char **uuid) +{ + struct grub_gpt_partentry entry; + + if (grub_gpt_device_partentry (device, &entry)) + return grub_errno; + + *uuid = grub_gpt_guid_to_str (&entry.guid); + if (!*uuid) + return grub_errno; + + return GRUB_ERR_NONE; +} + +static struct grub_gpt_header * +grub_gpt_get_header (grub_gpt_t gpt) +{ + if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) + return &gpt->primary; + else if (gpt->status & GRUB_GPT_BACKUP_HEADER_VALID) + return &gpt->backup; + + grub_error (GRUB_ERR_BUG, "No valid GPT header"); + return NULL; +} + +grub_err_t +grub_gpt_disk_uuid (grub_device_t device, char **uuid) +{ + struct grub_gpt_header *header; + + grub_gpt_t gpt = grub_gpt_read (device->disk); + if (!gpt) + goto done; + + header = grub_gpt_get_header (gpt); + if (!header) + goto done; + + *uuid = grub_gpt_guid_to_str (&header->guid); + +done: + grub_gpt_free (gpt); + return grub_errno; +} + +static grub_uint64_t +grub_gpt_size_to_sectors (grub_gpt_t gpt, grub_size_t size) +{ + unsigned int sector_size; + grub_uint64_t sectors; + + sector_size = 1U << gpt->log_sector_size; + sectors = size / sector_size; + if (size % sector_size) + sectors++; + + return sectors; +} + +/* Copied from grub-core/kern/disk_common.c grub_disk_adjust_range so we can + * avoid attempting to use disk->total_sectors when GRUB won't let us. + * TODO: Why is disk->total_sectors not set to GRUB_DISK_SIZE_UNKNOWN? */ +static int +grub_gpt_disk_size_valid (grub_disk_t disk) +{ + grub_disk_addr_t total_sectors; + + /* Transform total_sectors to number of 512B blocks. */ + total_sectors = disk->total_sectors << (disk->log_sector_size - GRUB_DISK_SECTOR_BITS); + + /* Some drivers have problems with disks above reasonable. + Treat unknown as 1EiB disk. While on it, clamp the size to 1EiB. + Just one condition is enough since GRUB_DISK_UNKNOWN_SIZE << ls is always + above 9EiB. + */ + if (total_sectors > (1ULL << 51)) + return 0; + + return 1; +} + +static void +grub_gpt_lecrc32 (grub_uint32_t *crc, const void *data, grub_size_t len) +{ + grub_uint32_t crc32_val; + + grub_crypto_hash (GRUB_MD_CRC32, &crc32_val, data, len); + + /* GRUB_MD_CRC32 always uses big endian, gpt is always little. */ + *crc = grub_swap_bytes32 (crc32_val); +} + +static void +grub_gpt_header_lecrc32 (grub_uint32_t *crc, struct grub_gpt_header *header) +{ + grub_uint32_t old, new; + + /* crc32 must be computed with the field cleared. */ + old = header->crc32; + header->crc32 = 0; + grub_gpt_lecrc32 (&new, header, sizeof (*header)); + header->crc32 = old; + + *crc = new; +} + +/* Make sure the MBR is a protective MBR and not a normal MBR. */ +grub_err_t +grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr) +{ + unsigned int i; + + if (mbr->signature != + grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE)) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid MBR signature"); + + for (i = 0; i < sizeof (mbr->entries); i++) + if (mbr->entries[i].type == GRUB_PC_PARTITION_TYPE_GPT_DISK) + return GRUB_ERR_NONE; + + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid protective MBR"); +} + +static grub_uint64_t +grub_gpt_entries_size (struct grub_gpt_header *gpt) +{ + return (grub_uint64_t) grub_le_to_cpu32 (gpt->maxpart) * + (grub_uint64_t) grub_le_to_cpu32 (gpt->partentry_size); +} + +static grub_uint64_t +grub_gpt_entries_sectors (struct grub_gpt_header *gpt, + unsigned int log_sector_size) +{ + grub_uint64_t sector_bytes, entries_bytes; + + sector_bytes = 1ULL << log_sector_size; + entries_bytes = grub_gpt_entries_size (gpt); + return grub_divmod64(entries_bytes + sector_bytes - 1, sector_bytes, NULL); +} + +static int +is_pow2 (grub_uint32_t n) +{ + return (n & (n - 1)) == 0; +} + +grub_err_t +grub_gpt_header_check (struct grub_gpt_header *gpt, + unsigned int log_sector_size) +{ + grub_uint32_t crc = 0, size; + grub_uint64_t start, end; + + if (grub_memcmp (gpt->magic, grub_gpt_magic, sizeof (grub_gpt_magic)) != 0) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT signature"); + + if (gpt->version != GRUB_GPT_HEADER_VERSION) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "unknown GPT version"); + + grub_gpt_header_lecrc32 (&crc, gpt); + if (gpt->crc32 != crc) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT header crc32"); + + /* The header size "must be greater than or equal to 92 and must be less + * than or equal to the logical block size." */ + size = grub_le_to_cpu32 (gpt->headersize); + if (size < 92U || size > (1U << log_sector_size)) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT header size"); + + /* The partition entry size must be "a value of 128*(2^n) where n is an + * integer greater than or equal to zero (e.g., 128, 256, 512, etc.)." */ + size = grub_le_to_cpu32 (gpt->partentry_size); + if (size < 128U || size % 128U || !is_pow2 (size / 128U)) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry size"); + + /* The minimum entries table size is specified in terms of bytes, + * regardless of how large the individual entry size is. */ + if (grub_gpt_entries_size (gpt) < GRUB_GPT_DEFAULT_ENTRIES_SIZE) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry table size"); + + /* And of course there better be some space for partitions! */ + start = grub_le_to_cpu64 (gpt->start); + end = grub_le_to_cpu64 (gpt->end); + if (start > end) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid usable sectors"); + + return GRUB_ERR_NONE; +} + +static int +grub_gpt_headers_equal (grub_gpt_t gpt) +{ + /* Assume headers passed grub_gpt_header_check so skip magic and version. + * Individual fields must be checked instead of just using memcmp because + * crc32, header, alternate, and partitions will all normally differ. */ + + if (gpt->primary.headersize != gpt->backup.headersize || + gpt->primary.header_lba != gpt->backup.alternate_lba || + gpt->primary.alternate_lba != gpt->backup.header_lba || + gpt->primary.start != gpt->backup.start || + gpt->primary.end != gpt->backup.end || + gpt->primary.maxpart != gpt->backup.maxpart || + gpt->primary.partentry_size != gpt->backup.partentry_size || + gpt->primary.partentry_crc32 != gpt->backup.partentry_crc32) + return 0; + + return grub_memcmp(&gpt->primary.guid, &gpt->backup.guid, + sizeof(grub_gpt_part_guid_t)) == 0; +} + +static grub_err_t +grub_gpt_check_primary (grub_gpt_t gpt) +{ + grub_uint64_t backup, primary, entries, entries_len, start, end; + + primary = grub_le_to_cpu64 (gpt->primary.header_lba); + backup = grub_le_to_cpu64 (gpt->primary.alternate_lba); + entries = grub_le_to_cpu64 (gpt->primary.partitions); + entries_len = grub_gpt_entries_sectors(&gpt->primary, gpt->log_sector_size); + start = grub_le_to_cpu64 (gpt->primary.start); + end = grub_le_to_cpu64 (gpt->primary.end); + + grub_dprintf ("gpt", "Primary GPT layout:\n" + "primary header = 0x%llx backup header = 0x%llx\n" + "entries location = 0x%llx length = 0x%llx\n" + "first usable = 0x%llx last usable = 0x%llx\n", + (unsigned long long) primary, + (unsigned long long) backup, + (unsigned long long) entries, + (unsigned long long) entries_len, + (unsigned long long) start, + (unsigned long long) end); + + if (grub_gpt_header_check (&gpt->primary, gpt->log_sector_size)) + return grub_errno; + if (primary != 1) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid primary GPT LBA"); + if (entries <= 1 || entries+entries_len > start) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid entries location"); + if (backup <= end) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid backup GPT LBA"); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_gpt_check_backup (grub_gpt_t gpt) +{ + grub_uint64_t backup, primary, entries, entries_len, start, end; + + backup = grub_le_to_cpu64 (gpt->backup.header_lba); + primary = grub_le_to_cpu64 (gpt->backup.alternate_lba); + entries = grub_le_to_cpu64 (gpt->backup.partitions); + entries_len = grub_gpt_entries_sectors(&gpt->backup, gpt->log_sector_size); + start = grub_le_to_cpu64 (gpt->backup.start); + end = grub_le_to_cpu64 (gpt->backup.end); + + grub_dprintf ("gpt", "Backup GPT layout:\n" + "primary header = 0x%llx backup header = 0x%llx\n" + "entries location = 0x%llx length = 0x%llx\n" + "first usable = 0x%llx last usable = 0x%llx\n", + (unsigned long long) primary, + (unsigned long long) backup, + (unsigned long long) entries, + (unsigned long long) entries_len, + (unsigned long long) start, + (unsigned long long) end); + + if (grub_gpt_header_check (&gpt->backup, gpt->log_sector_size)) + return grub_errno; + if (primary != 1) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid primary GPT LBA"); + if (entries <= end || entries+entries_len > backup) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid entries location"); + if (backup <= end) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid backup GPT LBA"); + + /* If both primary and backup are valid but differ prefer the primary. */ + if ((gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) && + !grub_gpt_headers_equal (gpt)) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "backup GPT out of sync"); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_gpt_read_primary (grub_disk_t disk, grub_gpt_t gpt) +{ + grub_disk_addr_t addr; + + /* TODO: The gpt partmap module searches for the primary header instead + * of relying on the disk's sector size. For now trust the disk driver + * but eventually this code should match the existing behavior. */ + gpt->log_sector_size = disk->log_sector_size; + + grub_dprintf ("gpt", "reading primary GPT from sector 0x1\n"); + + addr = grub_gpt_sector_to_addr (gpt, 1); + if (grub_disk_read (disk, addr, 0, sizeof (gpt->primary), &gpt->primary)) + return grub_errno; + + if (grub_gpt_check_primary (gpt)) + return grub_errno; + + gpt->status |= GRUB_GPT_PRIMARY_HEADER_VALID; + + if (grub_gpt_read_entries (disk, gpt, &gpt->primary, + &gpt->entries, &gpt->entries_size)) + return grub_errno; + + gpt->status |= GRUB_GPT_PRIMARY_ENTRIES_VALID; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_gpt_read_backup (grub_disk_t disk, grub_gpt_t gpt) +{ + void *entries = NULL; + grub_size_t entries_size; + grub_uint64_t sector; + grub_disk_addr_t addr; + + /* Assumes gpt->log_sector_size == disk->log_sector_size */ + if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) + { + sector = grub_le_to_cpu64 (gpt->primary.alternate_lba); + if (grub_gpt_disk_size_valid (disk) && sector >= disk->total_sectors) + return grub_error (GRUB_ERR_OUT_OF_RANGE, + "backup GPT located at 0x%llx, " + "beyond last disk sector at 0x%llx", + (unsigned long long) sector, + (unsigned long long) disk->total_sectors - 1); + } + else if (grub_gpt_disk_size_valid (disk)) + sector = disk->total_sectors - 1; + else + return grub_error (GRUB_ERR_OUT_OF_RANGE, + "size of disk unknown, cannot locate backup GPT"); + + grub_dprintf ("gpt", "reading backup GPT from sector 0x%llx\n", + (unsigned long long) sector); + + addr = grub_gpt_sector_to_addr (gpt, sector); + if (grub_disk_read (disk, addr, 0, sizeof (gpt->backup), &gpt->backup)) + return grub_errno; + + if (grub_gpt_check_backup (gpt)) + return grub_errno; + + /* Ensure the backup header thinks it is located where we found it. */ + if (grub_le_to_cpu64 (gpt->backup.header_lba) != sector) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid backup GPT LBA"); + + gpt->status |= GRUB_GPT_BACKUP_HEADER_VALID; + + if (grub_gpt_read_entries (disk, gpt, &gpt->backup, + &entries, &entries_size)) + return grub_errno; + + if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID) + { + if (entries_size != gpt->entries_size || + grub_memcmp (entries, gpt->entries, entries_size) != 0) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "backup GPT out of sync"); + + grub_free (entries); + } + else + { + gpt->entries = entries; + gpt->entries_size = entries_size; + } + + gpt->status |= GRUB_GPT_BACKUP_ENTRIES_VALID; + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt, + struct grub_gpt_header *header, + void **ret_entries, + grub_size_t *ret_entries_size) +{ + void *entries = NULL; + grub_uint32_t count, size, crc; + grub_uint64_t sector; + grub_disk_addr_t addr; + grub_size_t entries_size; + + /* Grub doesn't include calloc, hence the manual overflow check. */ + count = grub_le_to_cpu32 (header->maxpart); + size = grub_le_to_cpu32 (header->partentry_size); + entries_size = count *size; + if (size && entries_size / size != count) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory")); + goto fail; + } + + /* Double check that the header was validated properly. */ + if (entries_size < GRUB_GPT_DEFAULT_ENTRIES_SIZE) + return grub_error (GRUB_ERR_BUG, "invalid GPT entries table size"); + + entries = grub_malloc (entries_size); + if (!entries) + goto fail; + + sector = grub_le_to_cpu64 (header->partitions); + grub_dprintf ("gpt", "reading GPT %lu entries from sector 0x%llx\n", + (unsigned long) count, + (unsigned long long) sector); + + addr = grub_gpt_sector_to_addr (gpt, sector); + if (grub_disk_read (disk, addr, 0, entries_size, entries)) + goto fail; + + grub_gpt_lecrc32 (&crc, entries, entries_size); + if (crc != header->partentry_crc32) + { + grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry crc32"); + goto fail; + } + + *ret_entries = entries; + *ret_entries_size = entries_size; + return GRUB_ERR_NONE; + +fail: + grub_free (entries); + return grub_errno; +} + +grub_gpt_t +grub_gpt_read (grub_disk_t disk) +{ + grub_gpt_t gpt; + + grub_dprintf ("gpt", "reading GPT from %s\n", disk->name); + + gpt = grub_zalloc (sizeof (*gpt)); + if (!gpt) + goto fail; + + if (grub_disk_read (disk, 0, 0, sizeof (gpt->mbr), &gpt->mbr)) + goto fail; + + /* Check the MBR but errors aren't reported beyond the status bit. */ + if (grub_gpt_pmbr_check (&gpt->mbr)) + grub_errno = GRUB_ERR_NONE; + else + gpt->status |= GRUB_GPT_PROTECTIVE_MBR; + + /* If both the primary and backup fail report the primary's error. */ + if (grub_gpt_read_primary (disk, gpt)) + { + grub_error_push (); + grub_gpt_read_backup (disk, gpt); + grub_error_pop (); + } + else + grub_gpt_read_backup (disk, gpt); + + /* If either succeeded clear any possible error from the other. */ + if (grub_gpt_primary_valid (gpt) || grub_gpt_backup_valid (gpt)) + grub_errno = GRUB_ERR_NONE; + else + goto fail; + + return gpt; + +fail: + grub_gpt_free (gpt); + return NULL; +} + +struct grub_gpt_partentry * +grub_gpt_get_partentry (grub_gpt_t gpt, grub_uint32_t n) +{ + struct grub_gpt_header *header; + grub_size_t offset; + + header = grub_gpt_get_header (gpt); + if (!header) + return NULL; + + if (n >= grub_le_to_cpu32 (header->maxpart)) + return NULL; + + offset = (grub_size_t) grub_le_to_cpu32 (header->partentry_size) * n; + return (struct grub_gpt_partentry *) ((char *) gpt->entries + offset); +} + +grub_err_t +grub_gpt_repair (grub_disk_t disk, grub_gpt_t gpt) +{ + /* Skip if there is nothing to do. */ + if (grub_gpt_both_valid (gpt)) + return GRUB_ERR_NONE; + + grub_dprintf ("gpt", "repairing GPT for %s\n", disk->name); + + if (disk->log_sector_size != gpt->log_sector_size) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "GPT sector size must match disk sector size"); + + if (grub_gpt_primary_valid (gpt)) + { + grub_uint64_t backup_header; + + grub_dprintf ("gpt", "primary GPT is valid\n"); + + /* Relocate backup to end if disk if the disk has grown. */ + backup_header = grub_le_to_cpu64 (gpt->primary.alternate_lba); + if (grub_gpt_disk_size_valid (disk) && + disk->total_sectors - 1 > backup_header) + { + backup_header = disk->total_sectors - 1; + grub_dprintf ("gpt", "backup GPT header relocated to 0x%llx\n", + (unsigned long long) backup_header); + + gpt->primary.alternate_lba = grub_cpu_to_le64 (backup_header); + } + + grub_memcpy (&gpt->backup, &gpt->primary, sizeof (gpt->backup)); + gpt->backup.header_lba = gpt->primary.alternate_lba; + gpt->backup.alternate_lba = gpt->primary.header_lba; + gpt->backup.partitions = grub_cpu_to_le64 (backup_header - + grub_gpt_size_to_sectors (gpt, gpt->entries_size)); + } + else if (grub_gpt_backup_valid (gpt)) + { + grub_dprintf ("gpt", "backup GPT is valid\n"); + + grub_memcpy (&gpt->primary, &gpt->backup, sizeof (gpt->primary)); + gpt->primary.header_lba = gpt->backup.alternate_lba; + gpt->primary.alternate_lba = gpt->backup.header_lba; + gpt->primary.partitions = grub_cpu_to_le64_compile_time (2); + } + else + return grub_error (GRUB_ERR_BUG, "No valid GPT"); + + if (grub_gpt_update (gpt)) + return grub_errno; + + grub_dprintf ("gpt", "repairing GPT for %s successful\n", disk->name); + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_gpt_update (grub_gpt_t gpt) +{ + grub_uint32_t crc; + + /* Clear status bits, require revalidation of everything. */ + gpt->status &= ~(GRUB_GPT_PRIMARY_HEADER_VALID | + GRUB_GPT_PRIMARY_ENTRIES_VALID | + GRUB_GPT_BACKUP_HEADER_VALID | + GRUB_GPT_BACKUP_ENTRIES_VALID); + + /* Writing headers larger than our header structure are unsupported. */ + gpt->primary.headersize = + grub_cpu_to_le32_compile_time (sizeof (gpt->primary)); + gpt->backup.headersize = + grub_cpu_to_le32_compile_time (sizeof (gpt->backup)); + + grub_gpt_lecrc32 (&crc, gpt->entries, gpt->entries_size); + gpt->primary.partentry_crc32 = crc; + gpt->backup.partentry_crc32 = crc; + + grub_gpt_header_lecrc32 (&gpt->primary.crc32, &gpt->primary); + grub_gpt_header_lecrc32 (&gpt->backup.crc32, &gpt->backup); + + if (grub_gpt_check_primary (gpt)) + { + grub_error_push (); + return grub_error (GRUB_ERR_BUG, "Generated invalid GPT primary header"); + } + + gpt->status |= (GRUB_GPT_PRIMARY_HEADER_VALID | + GRUB_GPT_PRIMARY_ENTRIES_VALID); + + if (grub_gpt_check_backup (gpt)) + { + grub_error_push (); + return grub_error (GRUB_ERR_BUG, "Generated invalid GPT backup header"); + } + + gpt->status |= (GRUB_GPT_BACKUP_HEADER_VALID | + GRUB_GPT_BACKUP_ENTRIES_VALID); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_gpt_write_table (grub_disk_t disk, grub_gpt_t gpt, + struct grub_gpt_header *header) +{ + grub_disk_addr_t addr; + + if (grub_le_to_cpu32 (header->headersize) != sizeof (*header)) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + "Header size is %u, must be %u", + grub_le_to_cpu32 (header->headersize), + sizeof (*header)); + + addr = grub_gpt_sector_to_addr (gpt, grub_le_to_cpu64 (header->header_lba)); + if (addr == 0) + return grub_error (GRUB_ERR_BUG, + "Refusing to write GPT header to address 0x0"); + if (grub_disk_write (disk, addr, 0, sizeof (*header), header)) + return grub_errno; + + addr = grub_gpt_sector_to_addr (gpt, grub_le_to_cpu64 (header->partitions)); + if (addr < 2) + return grub_error (GRUB_ERR_BUG, + "Refusing to write GPT entries to address 0x%llx", + (unsigned long long) addr); + if (grub_disk_write (disk, addr, 0, gpt->entries_size, gpt->entries)) + return grub_errno; + + return GRUB_ERR_NONE; +} + +grub_err_t +grub_gpt_write (grub_disk_t disk, grub_gpt_t gpt) +{ + grub_uint64_t backup_header; + + /* TODO: update/repair protective MBRs too. */ + + if (!grub_gpt_both_valid (gpt)) + return grub_error (GRUB_ERR_BAD_PART_TABLE, "Invalid GPT data"); + + /* Write the backup GPT first so if writing fails the update is aborted + * and the primary is left intact. However if the backup location is + * inaccessible we have to just skip and hope for the best, the backup + * will need to be repaired in the OS. */ + backup_header = grub_le_to_cpu64 (gpt->backup.header_lba); + if (grub_gpt_disk_size_valid (disk) && + backup_header >= disk->total_sectors) + { + grub_printf ("warning: backup GPT located at 0x%llx, " + "beyond last disk sector at 0x%llx\n", + (unsigned long long) backup_header, + (unsigned long long) disk->total_sectors - 1); + grub_printf ("warning: only writing primary GPT, " + "the backup GPT must be repaired from the OS\n"); + } + else + { + grub_dprintf ("gpt", "writing backup GPT to %s\n", disk->name); + if (grub_gpt_write_table (disk, gpt, &gpt->backup)) + return grub_errno; + } + + grub_dprintf ("gpt", "writing primary GPT to %s\n", disk->name); + if (grub_gpt_write_table (disk, gpt, &gpt->primary)) + return grub_errno; + + return GRUB_ERR_NONE; +} + +void +grub_gpt_free (grub_gpt_t gpt) +{ + if (!gpt) + return; + + grub_free (gpt->entries); + grub_free (gpt); +} diff --git a/grub-core/loader/arm64/linux.c b/grub-core/loader/arm64/linux.c index ef3e9f944..17bed4e15 100644 --- a/grub-core/loader/arm64/linux.c +++ b/grub-core/loader/arm64/linux.c @@ -34,6 +34,8 @@ #include #include +#include + GRUB_MOD_LICENSE ("GPLv3+"); static grub_dl_t my_mod; @@ -333,7 +335,8 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), grub_dprintf ("linux", "kernel @ %p\n", kernel_addr); - cmdline_size = grub_loader_cmdline_size (argc, argv) + sizeof (LINUX_IMAGE); + cmdline_size = grub_loader_cmdline_size (argc, argv) + sizeof (LINUX_IMAGE) + + VERITY_CMDLINE_LENGTH; linux_args = grub_malloc (cmdline_size); if (!linux_args) { @@ -350,6 +353,7 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), if (grub_errno == GRUB_ERR_NONE) { + grub_pass_verity_hash (kernel_addr, linux_args, cmdline_size); grub_loader_set (grub_linux_boot, grub_linux_unload, 0); loaded = 1; } diff --git a/grub-core/loader/i386/efi/linux.c b/grub-core/loader/i386/efi/linux.c new file mode 100644 index 000000000..fc3d949c9 --- /dev/null +++ b/grub-core/loader/i386/efi/linux.c @@ -0,0 +1,363 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2012 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +GRUB_MOD_LICENSE ("GPLv3+"); + +static grub_dl_t my_mod; +static int loaded; +static void *kernel_mem; +static grub_uint64_t kernel_size; +static grub_uint8_t *initrd_mem; +static grub_uint32_t handover_offset; +struct linux_kernel_params *params; +static char *linux_cmdline; + +#define BYTES_TO_PAGES(bytes) (((bytes) + 0xfff) >> 12) + +#define SHIM_LOCK_GUID \ + { 0x605dab50, 0xe046, 0x4300, {0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23} } + +struct grub_efi_shim_lock +{ + grub_efi_status_t (*verify) (void *buffer, grub_uint32_t size); +}; +typedef struct grub_efi_shim_lock grub_efi_shim_lock_t; + +static grub_efi_boolean_t +grub_linuxefi_secure_validate (void *data, grub_uint32_t size) +{ + grub_efi_guid_t guid = SHIM_LOCK_GUID; + grub_efi_shim_lock_t *shim_lock; + + shim_lock = grub_efi_locate_protocol(&guid, NULL); + + if (!shim_lock) { + if (grub_efi_secure_boot()) + return 0; + else + return 1; + } + + if (shim_lock->verify(data, size) == GRUB_EFI_SUCCESS) + return 1; + + return 0; +} + +typedef void(*handover_func)(void *, grub_efi_system_table_t *, struct linux_kernel_params *); + +static grub_err_t +grub_linuxefi_boot (void) +{ + handover_func hf; + int offset = 0; + +#ifdef __x86_64__ + offset = 512; +#endif + + hf = (handover_func)((char *)kernel_mem + handover_offset + offset); + + asm volatile ("cli"); + + hf (grub_efi_image_handle, grub_efi_system_table, params); + + /* Not reached */ + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_linuxefi_unload (void) +{ + grub_dl_unref (my_mod); + loaded = 0; + if (initrd_mem) + grub_efi_free_pages((grub_efi_physical_address_t)initrd_mem, BYTES_TO_PAGES(params->ramdisk_size)); + if (linux_cmdline) + grub_efi_free_pages((grub_efi_physical_address_t)linux_cmdline, BYTES_TO_PAGES(params->cmdline_size + 1)); + if (kernel_mem) + grub_efi_free_pages((grub_efi_physical_address_t)kernel_mem, BYTES_TO_PAGES(kernel_size)); + if (params) + grub_efi_free_pages((grub_efi_physical_address_t)params, BYTES_TO_PAGES(16384)); + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t *files = 0; + int i, nfiles = 0; + grub_size_t size = 0; + grub_uint8_t *ptr; + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + if (!loaded) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first")); + goto fail; + } + + files = grub_zalloc (argc * sizeof (files[0])); + if (!files) + goto fail; + + for (i = 0; i < argc; i++) + { + grub_file_filter_disable_compression (); + files[i] = grub_file_open (argv[i]); + if (! files[i]) + goto fail; + nfiles++; + size += ALIGN_UP (grub_file_size (files[i]), 4); + } + + initrd_mem = grub_efi_allocate_pages_max (0x3fffffff, BYTES_TO_PAGES(size)); + + if (!initrd_mem) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("can't allocate initrd")); + goto fail; + } + + params->ramdisk_size = size; + params->ramdisk_image = (grub_uint32_t)(grub_uint64_t) initrd_mem; + + ptr = initrd_mem; + + for (i = 0; i < nfiles; i++) + { + grub_ssize_t cursize = grub_file_size (files[i]); + if (grub_file_read (files[i], ptr, cursize) != cursize) + { + if (!grub_errno) + grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"), + argv[i]); + goto fail; + } + // TODO figure out the GRUB_VERIFY_ equivalent for this one + //grub_tpm_measure (ptr, cursize, GRUB_BINARY_PCR, "Initrd"); + //grub_print_error(); + ptr += cursize; + grub_memset (ptr, 0, ALIGN_UP_OVERHEAD (cursize, 4)); + ptr += ALIGN_UP_OVERHEAD (cursize, 4); + } + + params->ramdisk_size = size; + + fail: + for (i = 0; i < nfiles; i++) + grub_file_close (files[i]); + grub_free (files); + + if (initrd_mem && grub_errno) + grub_efi_free_pages((grub_efi_physical_address_t)initrd_mem, BYTES_TO_PAGES(size)); + + return grub_errno; +} + +static grub_err_t +grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), + int argc, char *argv[]) +{ + grub_file_t file = 0; + struct linux_kernel_header lh; + grub_ssize_t len, start, filelen; + void *kernel = NULL; + + grub_dl_ref (my_mod); + + if (argc == 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); + goto fail; + } + + file = grub_file_open (argv[0]); + if (! file) + goto fail; + + filelen = grub_file_size (file); + + kernel = grub_malloc(filelen); + + if (!kernel) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("cannot allocate kernel buffer")); + goto fail; + } + + if (grub_file_read (file, kernel, filelen) != filelen) + { + grub_error (GRUB_ERR_FILE_READ_ERROR, N_("Can't read kernel %s"), argv[0]); + goto fail; + } + + // TODO figure out the GRUB_VERIFY_ equivalent for this one + //grub_tpm_measure (kernel, filelen, GRUB_BINARY_PCR, "Kernel"); + //grub_print_error(); + + if (! grub_linuxefi_secure_validate (kernel, filelen)) + { + grub_error (GRUB_ERR_INVALID_COMMAND, N_("%s has invalid signature"), argv[0]); + grub_free (kernel); + goto fail; + } + + params = grub_efi_allocate_pages_max (0x3fffffff, BYTES_TO_PAGES(16384)); + + if (! params) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate kernel parameters"); + goto fail; + } + + grub_memset (params, 0, 16384); + + grub_memcpy (&lh, kernel, sizeof (lh)); + + if (lh.boot_flag != grub_cpu_to_le16 (0xaa55)) + { + grub_error (GRUB_ERR_BAD_OS, N_("invalid magic number")); + goto fail; + } + + if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS) + { + grub_error (GRUB_ERR_BAD_OS, N_("too many setup sectors")); + goto fail; + } + + if (lh.version < grub_cpu_to_le16 (0x020b)) + { + grub_error (GRUB_ERR_BAD_OS, N_("kernel too old")); + goto fail; + } + + if (!lh.handover_offset) + { + grub_error (GRUB_ERR_BAD_OS, N_("kernel doesn't support EFI handover")); + goto fail; + } + + linux_cmdline = grub_efi_allocate_pages_max(0x3fffffff, + BYTES_TO_PAGES(lh.cmdline_size + 1)); + + if (!linux_cmdline) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("can't allocate cmdline")); + goto fail; + } + + grub_memcpy (linux_cmdline, LINUX_IMAGE, sizeof (LINUX_IMAGE)); + grub_create_loader_cmdline (argc, argv, + linux_cmdline + sizeof (LINUX_IMAGE) - 1, + lh.cmdline_size - (sizeof (LINUX_IMAGE) - 1)); + + grub_pass_verity_hash(&lh, linux_cmdline, lh.cmdline_size); + lh.cmd_line_ptr = (grub_uint32_t)(grub_uint64_t)linux_cmdline; + + handover_offset = lh.handover_offset; + + start = (lh.setup_sects + 1) * 512; + len = grub_file_size(file) - start; + + kernel_mem = grub_efi_allocate_pages(lh.pref_address, + BYTES_TO_PAGES(lh.init_size)); + + if (!kernel_mem) + kernel_mem = grub_efi_allocate_pages_max(0x3fffffff, + BYTES_TO_PAGES(lh.init_size)); + + if (!kernel_mem) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("can't allocate kernel")); + goto fail; + } + + grub_memcpy (kernel_mem, (char *)kernel + start, len); + grub_loader_set (grub_linuxefi_boot, grub_linuxefi_unload, 0); + loaded=1; + + lh.code32_start = (grub_uint32_t)(grub_uint64_t) kernel_mem; + grub_memcpy (params, &lh, 2 * 512); + + params->type_of_loader = 0x21; + + fail: + + if (file) + grub_file_close (file); + + grub_free (kernel); + + if (grub_errno != GRUB_ERR_NONE) + { + grub_dl_unref (my_mod); + loaded = 0; + } + + if (linux_cmdline && !loaded) + grub_efi_free_pages((grub_efi_physical_address_t)linux_cmdline, BYTES_TO_PAGES(lh.cmdline_size + 1)); + + if (kernel_mem && !loaded) + grub_efi_free_pages((grub_efi_physical_address_t)kernel_mem, BYTES_TO_PAGES(kernel_size)); + + if (params && !loaded) + grub_efi_free_pages((grub_efi_physical_address_t)params, BYTES_TO_PAGES(16384)); + + return grub_errno; +} + +static grub_command_t cmd_linux, cmd_initrd; + +GRUB_MOD_INIT(linuxefi) +{ + cmd_linux = + grub_register_command ("linuxefi", grub_cmd_linux, + 0, N_("Load Linux.")); + cmd_initrd = + grub_register_command ("initrdefi", grub_cmd_initrd, + 0, N_("Load initrd.")); + my_mod = mod; +} + +GRUB_MOD_FINI(linuxefi) +{ + grub_unregister_command (cmd_linux); + grub_unregister_command (cmd_initrd); +} diff --git a/grub-core/loader/i386/linux.c b/grub-core/loader/i386/linux.c index d0501e229..98729ba0e 100644 --- a/grub-core/loader/i386/linux.c +++ b/grub-core/loader/i386/linux.c @@ -36,6 +36,9 @@ #include #include #include +#include + +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -642,12 +645,13 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), grub_file_t file = 0; struct linux_i386_kernel_header lh; grub_uint8_t setup_sects; - grub_size_t real_size, prot_size, prot_file_size; + grub_size_t real_size, prot_size, prot_file_size, kernel_offset; grub_ssize_t len; int i; grub_size_t align, min_align; int relocatable; grub_uint64_t preferred_address = GRUB_LINUX_BZIMAGE_ADDR; + grub_uint8_t *kernel = NULL; grub_dl_ref (my_mod); @@ -661,7 +665,15 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), if (! file) goto fail; - if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) + len = grub_file_size (file); + kernel = grub_malloc (len); + if (!kernel) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("cannot allocate kernel buffer")); + goto fail; + } + + if (grub_file_read (file, kernel, len) != len) { if (!grub_errno) grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), @@ -669,6 +681,14 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), goto fail; } + // TODO figure out the GRUB_VERIFY_ equivalent for this one + //grub_tpm_measure (kernel, len, GRUB_BINARY_PCR, "Kernel"); + //grub_print_error(); + + grub_memcpy (&lh, kernel, sizeof (lh)); + + kernel_offset = sizeof (lh); + if (lh.boot_flag != grub_cpu_to_le16_compile_time (0xaa55)) { grub_error (GRUB_ERR_BAD_OS, "invalid magic number"); @@ -789,6 +809,7 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), argv[0]); goto fail; } + kernel_offset += len; linux_params.type_of_loader = GRUB_LINUX_BOOT_LOADER_TYPE; @@ -847,7 +868,7 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), /* The other parameters are filled when booting. */ - grub_file_seek (file, real_size + GRUB_DISK_SECTOR_SIZE); + kernel_offset = real_size + GRUB_DISK_SECTOR_SIZE; grub_dprintf ("linux", "bzImage, setup=0x%x, size=0x%x\n", (unsigned) real_size, (unsigned) prot_size); @@ -1000,10 +1021,10 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), goto fail; } + grub_pass_verity_hash(&lh, linux_cmdline, maximal_cmdline_size); len = prot_file_size; - if (grub_file_read (file, prot_mode_mem, len) != len && !grub_errno) - grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), - argv[0]); + grub_memcpy (prot_mode_mem, kernel + kernel_offset, len); + kernel_offset += len; if (grub_errno == GRUB_ERR_NONE) { @@ -1014,6 +1035,8 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), fail: + grub_free (kernel); + if (file) grub_file_close (file); diff --git a/grub-core/loader/i386/multiboot_mbi.c b/grub-core/loader/i386/multiboot_mbi.c index ad3cc292f..468349f84 100644 --- a/grub-core/loader/i386/multiboot_mbi.c +++ b/grub-core/loader/i386/multiboot_mbi.c @@ -36,6 +36,7 @@ #include #include #include +#include #ifdef GRUB_MACHINE_EFI #include @@ -173,6 +174,10 @@ grub_multiboot_load (grub_file_t file, const char *filename) return grub_errno; } + // TODO figure out the GRUB_VERIFY_ equivalent for this one + //grub_tpm_measure((unsigned char*)buffer, len, GRUB_BINARY_PCR, filename); + //grub_print_error(); + header = find_header (buffer, len); if (header == 0) diff --git a/grub-core/loader/i386/pc/linux.c b/grub-core/loader/i386/pc/linux.c index 47ea2945e..c25e0dad1 100644 --- a/grub-core/loader/i386/pc/linux.c +++ b/grub-core/loader/i386/pc/linux.c @@ -35,6 +35,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -123,13 +124,14 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), grub_file_t file = 0; struct linux_i386_kernel_header lh; grub_uint8_t setup_sects; - grub_size_t real_size; + grub_size_t real_size, kernel_offset = 0; grub_ssize_t len; int i; char *grub_linux_prot_chunk; int grub_linux_is_bzimage; grub_addr_t grub_linux_prot_target; grub_err_t err; + grub_uint8_t *kernel = NULL; grub_dl_ref (my_mod); @@ -143,7 +145,15 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), if (! file) goto fail; - if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh)) + len = grub_file_size (file); + kernel = grub_malloc (len); + if (!kernel) + { + grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("cannot allocate kernel buffer")); + goto fail; + } + + if (grub_file_read (file, kernel, len) != len) { if (!grub_errno) grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), @@ -151,6 +161,13 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), goto fail; } + // TODO figure out the GRUB_VERIFY_ equivalent for this one + //grub_tpm_measure (kernel, len, GRUB_BINARY_PCR, "Kernel"); + //grub_print_error(); + + grub_memcpy (&lh, kernel, sizeof (lh)); + kernel_offset = sizeof (lh); + if (lh.boot_flag != grub_cpu_to_le16_compile_time (0xaa55)) { grub_error (GRUB_ERR_BAD_OS, "invalid magic number"); @@ -314,13 +331,9 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), grub_memmove (grub_linux_real_chunk, &lh, sizeof (lh)); len = real_size + GRUB_DISK_SECTOR_SIZE - sizeof (lh); - if (grub_file_read (file, grub_linux_real_chunk + sizeof (lh), len) != len) - { - if (!grub_errno) - grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), - argv[0]); - goto fail; - } + grub_memcpy (grub_linux_real_chunk + sizeof (lh), kernel + kernel_offset, + len); + kernel_offset += len; if (lh.header != grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE) || grub_le_to_cpu16 (lh.version) < 0x0200) @@ -370,6 +383,8 @@ grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), fail: + grub_free (kernel); + if (file) grub_file_close (file); diff --git a/grub-core/loader/i386/xen.c b/grub-core/loader/i386/xen.c index 8f662c8ac..bb9508ce9 100644 --- a/grub-core/loader/i386/xen.c +++ b/grub-core/loader/i386/xen.c @@ -656,7 +656,9 @@ grub_cmd_xen (grub_command_t cmd __attribute__ ((unused)), if (!file) return grub_errno; - elf = grub_xen_file (file); + elf = grub_xen_file_and_cmdline (file, + (char *) xen_state.next_start.cmd_line, + sizeof (xen_state.next_start.cmd_line) - 1); if (!elf) goto fail; diff --git a/grub-core/loader/i386/xen_file.c b/grub-core/loader/i386/xen_file.c index 9af5d66df..defb4fedc 100644 --- a/grub-core/loader/i386/xen_file.c +++ b/grub-core/loader/i386/xen_file.c @@ -19,11 +19,20 @@ #include #include #include +#include #define XZ_MAGIC "\3757zXZ\0" grub_elf_t grub_xen_file (grub_file_t file) +{ + return grub_xen_file_and_cmdline (file, NULL, 0); +} + +grub_elf_t +grub_xen_file_and_cmdline (grub_file_t file, + char *cmdline, + grub_size_t cmdline_max_len) { grub_elf_t elf; struct linux_i386_kernel_header lh; @@ -64,6 +73,9 @@ grub_xen_file (grub_file_t file) (unsigned long long) payload_offset, (unsigned long long) lh.payload_length); + if (cmdline) + grub_pass_verity_hash (&lh, cmdline, cmdline_max_len); + grub_file_seek (file, payload_offset); if (grub_file_read (file, &magic, sizeof (magic)) != sizeof (magic)) diff --git a/grub-core/loader/linux.c b/grub-core/loader/linux.c index 471b214d6..4b185cb15 100644 --- a/grub-core/loader/linux.c +++ b/grub-core/loader/linux.c @@ -4,6 +4,7 @@ #include #include #include +#include struct newc_head { @@ -288,6 +289,10 @@ grub_initrd_load (struct grub_linux_initrd_context *initrd_ctx, grub_initrd_close (initrd_ctx); return grub_errno; } + // TODO figure out the GRUB_VERIFY_ equivalent for this one + //grub_tpm_measure (ptr, cursize, GRUB_BINARY_PCR, "Initrd"); + //grub_print_error(); + ptr += cursize; } if (newc) diff --git a/grub-core/loader/multiboot.c b/grub-core/loader/multiboot.c index 4a98d7082..c16489de9 100644 --- a/grub-core/loader/multiboot.c +++ b/grub-core/loader/multiboot.c @@ -50,6 +50,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -437,6 +438,9 @@ grub_cmd_module (grub_command_t cmd __attribute__ ((unused)), } grub_file_close (file); + // TODO figure out the GRUB_VERIFY_ equivalent for this one + //grub_tpm_measure (module, size, GRUB_BINARY_PCR, argv[0]); + //grub_print_error(); return GRUB_ERR_NONE; } diff --git a/grub-core/loader/multiboot_mbi2.c b/grub-core/loader/multiboot_mbi2.c index 53da78615..ab8d99c28 100644 --- a/grub-core/loader/multiboot_mbi2.c +++ b/grub-core/loader/multiboot_mbi2.c @@ -36,6 +36,7 @@ #include #include #include +#include #if defined (GRUB_MACHINE_EFI) #include @@ -131,6 +132,10 @@ grub_multiboot2_load (grub_file_t file, const char *filename) COMPILE_TIME_ASSERT (MULTIBOOT_HEADER_ALIGN % 4 == 0); + // TODO figure out the GRUB_VERIFY_ equivalent for this one + //grub_tpm_measure ((unsigned char *)mld.buffer, len, GRUB_BINARY_PCR, filename); + //grub_print_error(); + header = find_header (mld.buffer, len); if (header == 0) diff --git a/grub-core/net/bootp.c b/grub-core/net/bootp.c index 04cfbb045..e81014f6f 100644 --- a/grub-core/net/bootp.c +++ b/grub-core/net/bootp.c @@ -25,6 +25,20 @@ #include #include +#if !defined(GRUB_MACHINE_EFI) && (defined(__i386__) || defined(__x86_64__)) +#define GRUB_NET_BOOTP_ARCH 0x0000 +#elif defined(GRUB_MACHINE_EFI) && defined(__x86_64__) +#define GRUB_NET_BOOTP_ARCH 0x0007 +#elif defined(GRUB_MACHINE_EFI) && defined(__aarch64__) +#define GRUB_NET_BOOTP_ARCH 0x000B +#else +#error "unknown bootp architecture" +#endif + +static grub_uint8_t grub_userclass[] = {0x4D, 0x06, 0x05, 'G', 'R', 'U', 'B', '2'}; +static grub_uint8_t grub_dhcpdiscover[] = {0x35, 0x01, 0x01}; +static grub_uint8_t grub_dhcptime[] = {0x33, 0x04, 0x00, 0x00, 0x0e, 0x10}; + struct grub_dhcp_discover_options { grub_uint8_t magic[4]; @@ -90,11 +104,6 @@ enum /* Max timeout when waiting for BOOTP/DHCP reply */ #define GRUB_DHCP_MAX_PACKET_TIMEOUT 32 -#define GRUB_BOOTP_MAX_OPTIONS_SIZE 64 - -/* Max timeout when waiting for BOOTP/DHCP reply */ -#define GRUB_DHCP_MAX_PACKET_TIMEOUT 32 - static const void * find_dhcp_option (const struct grub_net_bootp_packet *bp, grub_size_t size, grub_uint8_t opt_code, grub_uint8_t *opt_len) @@ -429,6 +438,7 @@ send_dhcp_packet (struct grub_net_network_level_interface *iface) struct udphdr *udph; grub_net_network_level_address_t target; grub_net_link_level_address_t ll_target; + grub_uint8_t *offset; static struct grub_dhcp_discover_options discover_options = { @@ -488,19 +498,31 @@ send_dhcp_packet (struct grub_net_network_level_interface *iface) COMPILE_TIME_ASSERT (sizeof (discover_options) <= GRUB_BOOTP_MAX_OPTIONS_SIZE); COMPILE_TIME_ASSERT (sizeof (request_options) <= GRUB_BOOTP_MAX_OPTIONS_SIZE); - nb = grub_netbuff_alloc (sizeof (*pack) + GRUB_BOOTP_MAX_OPTIONS_SIZE + 128); + nb = grub_netbuff_alloc (sizeof (*pack) + sizeof(grub_userclass) + + sizeof(grub_dhcpdiscover) + + sizeof(grub_dhcptime) + + GRUB_BOOTP_MAX_OPTIONS_SIZE + 128); if (!nb) return grub_errno; - err = grub_netbuff_reserve (nb, sizeof (*pack) + GRUB_BOOTP_MAX_OPTIONS_SIZE + 128); + err = grub_netbuff_reserve (nb, sizeof (*pack) + sizeof(grub_userclass) + + sizeof(grub_dhcpdiscover) + + sizeof(grub_dhcptime) + + GRUB_BOOTP_MAX_OPTIONS_SIZE + 128); if (err) goto out; - err = grub_netbuff_push (nb, GRUB_BOOTP_MAX_OPTIONS_SIZE); + err = grub_netbuff_push (nb, sizeof(grub_userclass) + + sizeof(grub_dhcpdiscover) + + sizeof(grub_dhcptime) + + GRUB_BOOTP_MAX_OPTIONS_SIZE); if (err) goto out; - grub_memset (nb->data, 0, GRUB_BOOTP_MAX_OPTIONS_SIZE); + grub_memset (nb->data, 0, sizeof(grub_userclass) + + sizeof(grub_dhcpdiscover) + + sizeof(grub_dhcptime) + + GRUB_BOOTP_MAX_OPTIONS_SIZE); if (!iface->srv_id) { grub_memcpy (nb->data, &discover_options, sizeof (discover_options)); @@ -537,6 +559,22 @@ send_dhcp_packet (struct grub_net_network_level_interface *iface) pack->ident = iface->xid; grub_memcpy (&pack->mac_addr, &iface->hwaddress.mac, 6); + offset = (grub_uint8_t *)&pack->vendor; + grub_memcpy (offset, grub_dhcpdiscover, sizeof(grub_dhcpdiscover)); + offset += sizeof(grub_dhcpdiscover); + grub_memcpy (offset, grub_userclass, sizeof(grub_userclass)); + offset += sizeof(grub_userclass); + grub_memcpy (offset, grub_dhcptime, sizeof(grub_dhcptime)); + + /* insert Client System Architecture (option 93) */ + offset += sizeof(grub_dhcptime); + offset[0] = 93; + offset[1] = 2; + offset[2] = (GRUB_NET_BOOTP_ARCH >> 8); + offset[3] = (GRUB_NET_BOOTP_ARCH & 0xFF); + + /* option terminator */ + offset[4] = 255; grub_netbuff_push (nb, sizeof (*udph)); diff --git a/grub-core/net/dns.c b/grub-core/net/dns.c index 5d9afe093..5deb1efd3 100644 --- a/grub-core/net/dns.c +++ b/grub-core/net/dns.c @@ -285,8 +285,8 @@ recv_hook (grub_net_udp_socket_t sock __attribute__ ((unused)), ptr++; ptr += 4; } - *data->addresses = grub_malloc (sizeof ((*data->addresses)[0]) - * grub_be_to_cpu16 (head->ancount)); + *data->addresses = grub_realloc (*data->addresses, sizeof ((*data->addresses)[0]) + * (grub_be_to_cpu16 (head->ancount) + *data->naddresses)); if (!*data->addresses) { grub_errno = GRUB_ERR_NONE; diff --git a/grub-core/net/http.c b/grub-core/net/http.c index 5aa4ad3be..389a78ee0 100644 --- a/grub-core/net/http.c +++ b/grub-core/net/http.c @@ -309,7 +309,7 @@ http_establish (struct grub_file *file, grub_off_t offset, int initial) { http_data_t data = file->data; grub_uint8_t *ptr; - int i; + int i, port; struct grub_net_buff *nb; grub_err_t err; @@ -390,8 +390,12 @@ http_establish (struct grub_file *file, grub_off_t offset, int initial) grub_netbuff_put (nb, 2); grub_memcpy (ptr, "\r\n", 2); + if (file->device->net->port) + port = file->device->net->port; + else + port = HTTP_PORT; data->sock = grub_net_tcp_open (file->device->net->server, - HTTP_PORT, http_receive, + port, http_receive, http_err, http_err, file); if (!data->sock) diff --git a/grub-core/net/net.c b/grub-core/net/net.c index d5d726a31..c474edac3 100644 --- a/grub-core/net/net.c +++ b/grub-core/net/net.c @@ -1261,7 +1261,7 @@ grub_net_open_real (const char *name) grub_net_app_level_t proto; const char *protname, *server; grub_size_t protnamelen; - int try; + int try, port = 0; if (grub_strncmp (name, "pxe:", sizeof ("pxe:") - 1) == 0) { @@ -1278,7 +1278,18 @@ grub_net_open_real (const char *name) else { const char *comma; + char *colon; comma = grub_strchr (name, ','); + if (!comma) + { + comma = grub_strchr (name, ';'); + } + colon = grub_strchr (name, ':'); + if (colon) + { + port = (int) grub_strtol(colon+1, NULL, 10); + *colon = '\0'; + } if (comma) { protnamelen = comma - name; @@ -1313,8 +1324,13 @@ grub_net_open_real (const char *name) ret->server = grub_strdup (server); if (!ret->server) { - grub_free (ret); - return NULL; + ret->server = grub_strdup (server); + ret->port = port; + if (!ret->server) + { + grub_free (ret); + return NULL; + } } ret->fs = &grub_net_fs; return ret; diff --git a/grub-core/script/execute.c b/grub-core/script/execute.c index ee299fd0e..0144c5698 100644 --- a/grub-core/script/execute.c +++ b/grub-core/script/execute.c @@ -27,6 +27,7 @@ #include #include #include +#include #include /* Max digits for a char is 3 (0xFF is 255), similarly for an int it diff --git a/include/grub/efi/api.h b/include/grub/efi/api.h index addcbfa8f..576074384 100644 --- a/include/grub/efi/api.h +++ b/include/grub/efi/api.h @@ -314,6 +314,11 @@ { 0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \ } +#define GRUB_EFI_SMBIOS3_TABLE_GUID \ + { 0xf2fd1544, 0x9794, 0x4a2c, \ + { 0x99, 0x2e, 0xe5, 0xbb, 0xcf, 0x20, 0xe3, 0x94 } \ + } + #define GRUB_EFI_SAL_TABLE_GUID \ { 0xeb9d2d32, 0x2d88, 0x11d3, \ { 0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \ diff --git a/include/grub/efi/efi.h b/include/grub/efi/efi.h index e90e00dc4..6b47373db 100644 --- a/include/grub/efi/efi.h +++ b/include/grub/efi/efi.h @@ -46,7 +46,8 @@ void * EXPORT_FUNC(grub_efi_allocate_fixed) (grub_efi_physical_address_t address, grub_efi_uintn_t pages); void * -EXPORT_FUNC(grub_efi_allocate_any_pages) (grub_efi_uintn_t pages); +EXPORT_FUNC(grub_efi_allocate_pages_max) (grub_efi_physical_address_t max, + grub_efi_uintn_t pages); void EXPORT_FUNC(grub_efi_free_pages) (grub_efi_physical_address_t address, grub_efi_uintn_t pages); grub_efi_uintn_t EXPORT_FUNC(grub_efi_find_mmap_size) (void); @@ -82,6 +83,7 @@ EXPORT_FUNC (grub_efi_set_variable) (const char *var, const grub_efi_guid_t *guid, void *data, grub_size_t datasize); +grub_efi_boolean_t EXPORT_FUNC (grub_efi_secure_boot) (void); int EXPORT_FUNC (grub_efi_compare_device_paths) (const grub_efi_device_path_t *dp1, const grub_efi_device_path_t *dp2); diff --git a/include/grub/efi/tpm.h b/include/grub/efi/tpm.h index 3ea6b4de1..51ab02e27 100644 --- a/include/grub/efi/tpm.h +++ b/include/grub/efi/tpm.h @@ -132,14 +132,12 @@ typedef struct tdEFI_TCG2_BOOT_SERVICE_CAPABILITY EFI_TCG2_BOOT_SERVICE_CAPABILI typedef grub_efi_uint32_t TCG_PCRINDEX; typedef grub_efi_uint32_t TCG_EVENTTYPE; -struct tdEFI_TCG2_EVENT_HEADER -{ +typedef struct tdEFI_TCG2_EVENT_HEADER { grub_efi_uint32_t HeaderSize; grub_efi_uint16_t HeaderVersion; - TCG_PCRINDEX PCRIndex; - TCG_EVENTTYPE EventType; -} GRUB_PACKED; -typedef struct tdEFI_TCG2_EVENT_HEADER EFI_TCG2_EVENT_HEADER; + TCG_PCRINDEX PCRIndex; + TCG_EVENTTYPE EventType; +} GRUB_PACKED EFI_TCG2_EVENT_HEADER; struct tdEFI_TCG2_EVENT { @@ -161,7 +159,7 @@ struct grub_efi_tpm2_protocol EventLogLocation, grub_efi_physical_address_t * EventLogLastEntry, - grub_efi_boolean_t * EventLogTruncated); + grub_efi_boolean_t *EventLogTruncated); grub_efi_status_t (*hash_log_extend_event) (struct grub_efi_tpm2_protocol * this, grub_efi_uint64_t Flags, grub_efi_physical_address_t diff --git a/include/grub/gpt_partition.h b/include/grub/gpt_partition.h index 7a93f4329..cdb6dfe8f 100644 --- a/include/grub/gpt_partition.h +++ b/include/grub/gpt_partition.h @@ -21,6 +21,7 @@ #include #include +#include struct grub_gpt_part_guid { @@ -31,24 +32,43 @@ struct grub_gpt_part_guid } GRUB_PACKED; typedef struct grub_gpt_part_guid grub_gpt_part_guid_t; -#define GRUB_GPT_PARTITION_TYPE_EMPTY \ - { 0x0, 0x0, 0x0, \ - { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } \ +/* Format the raw little-endian GUID as a newly allocated string. */ +char * grub_gpt_guid_to_str (grub_gpt_part_guid_t *guid); + + +#define GRUB_GPT_GUID_INIT(a, b, c, d1, d2, d3, d4, d5, d6, d7, d8) \ + { \ + grub_cpu_to_le32_compile_time (a), \ + grub_cpu_to_le16_compile_time (b), \ + grub_cpu_to_le16_compile_time (c), \ + { d1, d2, d3, d4, d5, d6, d7, d8 } \ } +#define GRUB_GPT_PARTITION_TYPE_EMPTY \ + GRUB_GPT_GUID_INIT (0x0, 0x0, 0x0, \ + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0) + +#define GRUB_GPT_PARTITION_TYPE_EFI_SYSTEM \ + GRUB_GPT_GUID_INIT (0xc12a7328, 0xf81f, 0x11d2, \ + 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b) + #define GRUB_GPT_PARTITION_TYPE_BIOS_BOOT \ - { grub_cpu_to_le32_compile_time (0x21686148), \ - grub_cpu_to_le16_compile_time (0x6449), \ - grub_cpu_to_le16_compile_time (0x6e6f), \ - { 0x74, 0x4e, 0x65, 0x65, 0x64, 0x45, 0x46, 0x49 } \ - } + GRUB_GPT_GUID_INIT (0x21686148, 0x6449, 0x6e6f, \ + 0x74, 0x4e, 0x65, 0x65, 0x64, 0x45, 0x46, 0x49) #define GRUB_GPT_PARTITION_TYPE_LDM \ - { grub_cpu_to_le32_compile_time (0x5808C8AAU),\ - grub_cpu_to_le16_compile_time (0x7E8F), \ - grub_cpu_to_le16_compile_time (0x42E0), \ - { 0x85, 0xD2, 0xE1, 0xE9, 0x04, 0x34, 0xCF, 0xB3 } \ - } + GRUB_GPT_GUID_INIT (0x5808c8aa, 0x7e8f, 0x42e0, \ + 0x85, 0xd2, 0xe1, 0xe9, 0x04, 0x34, 0xcf, 0xb3) + +#define GRUB_GPT_PARTITION_TYPE_USR_X86_64 \ + GRUB_GPT_GUID_INIT (0x5dfbf5f4, 0x2848, 0x4bac, \ + 0xaa, 0x5e, 0x0d, 0x9a, 0x20, 0xb7, 0x45, 0xa6) + +#define GRUB_GPT_HEADER_MAGIC \ + { 0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54 } + +#define GRUB_GPT_HEADER_VERSION \ + grub_cpu_to_le32_compile_time (0x00010000U) struct grub_gpt_header { @@ -57,11 +77,11 @@ struct grub_gpt_header grub_uint32_t headersize; grub_uint32_t crc32; grub_uint32_t unused1; - grub_uint64_t primary; - grub_uint64_t backup; + grub_uint64_t header_lba; + grub_uint64_t alternate_lba; grub_uint64_t start; grub_uint64_t end; - grub_uint8_t guid[16]; + grub_gpt_part_guid_t guid; grub_uint64_t partitions; grub_uint32_t maxpart; grub_uint32_t partentry_size; @@ -75,13 +95,172 @@ struct grub_gpt_partentry grub_uint64_t start; grub_uint64_t end; grub_uint64_t attrib; - char name[72]; + grub_uint16_t name[36]; } GRUB_PACKED; +enum grub_gpt_part_attr_offset +{ + /* Standard partition attribute bits defined by UEFI. */ + GRUB_GPT_PART_ATTR_OFFSET_REQUIRED = 0, + GRUB_GPT_PART_ATTR_OFFSET_NO_BLOCK_IO_PROTOCOL = 1, + GRUB_GPT_PART_ATTR_OFFSET_LEGACY_BIOS_BOOTABLE = 2, + + /* De facto standard attribute bits defined by Microsoft and reused by + * http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec */ + GRUB_GPT_PART_ATTR_OFFSET_READ_ONLY = 60, + GRUB_GPT_PART_ATTR_OFFSET_NO_AUTO = 63, + + /* Partition attributes for priority based selection, + * Currently only valid for PARTITION_TYPE_USR_X86_64. + * TRIES_LEFT and PRIORITY are 4 bit wide fields. */ + GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_PRIORITY = 48, + GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_TRIES_LEFT = 52, + GRUB_GPT_PART_ATTR_OFFSET_GPTPRIO_SUCCESSFUL = 56, +}; + +/* Helpers for reading/writing partition attributes. */ +static inline grub_uint64_t +grub_gpt_entry_attribute (struct grub_gpt_partentry *entry, + enum grub_gpt_part_attr_offset offset, + unsigned int bits) +{ + grub_uint64_t attrib = grub_le_to_cpu64 (entry->attrib); + + return (attrib >> offset) & ((1ULL << bits) - 1); +} + +static inline void +grub_gpt_entry_set_attribute (struct grub_gpt_partentry *entry, + grub_uint64_t value, + enum grub_gpt_part_attr_offset offset, + unsigned int bits) +{ + grub_uint64_t attrib, mask; + + mask = (((1ULL << bits) - 1) << offset); + attrib = grub_le_to_cpu64 (entry->attrib) & ~mask; + attrib |= ((value << offset) & mask); + entry->attrib = grub_cpu_to_le64 (attrib); +} + +/* Basic GPT partmap module. */ grub_err_t grub_gpt_partition_map_iterate (grub_disk_t disk, grub_partition_iterate_hook_t hook, void *hook_data); +/* Advanced GPT library. */ + +/* Status bits for the grub_gpt.status field. */ +#define GRUB_GPT_PROTECTIVE_MBR 0x01 +#define GRUB_GPT_HYBRID_MBR 0x02 +#define GRUB_GPT_PRIMARY_HEADER_VALID 0x04 +#define GRUB_GPT_PRIMARY_ENTRIES_VALID 0x08 +#define GRUB_GPT_BACKUP_HEADER_VALID 0x10 +#define GRUB_GPT_BACKUP_ENTRIES_VALID 0x20 + +/* UEFI requires the entries table to be at least 16384 bytes for a + * total of 128 entries given the standard 128 byte entry size. */ +#define GRUB_GPT_DEFAULT_ENTRIES_SIZE 16384 +#define GRUB_GPT_DEFAULT_ENTRIES_LENGTH \ + (GRUB_GPT_DEFAULT_ENTRIES_SIZE / sizeof (struct grub_gpt_partentry)) + +struct grub_gpt +{ + /* Bit field indicating which structures on disk are valid. */ + unsigned status; + + /* Protective or hybrid MBR. */ + struct grub_msdos_partition_mbr mbr; + + /* Each of the two GPT headers. */ + struct grub_gpt_header primary; + struct grub_gpt_header backup; + + /* Only need one entries table, on disk both copies are identical. + * The on disk entry size may be larger than our partentry struct so + * the table cannot be indexed directly. */ + void *entries; + grub_size_t entries_size; + + /* Logarithm of sector size, in case GPT and disk driver disagree. */ + unsigned int log_sector_size; +}; +typedef struct grub_gpt *grub_gpt_t; + +/* Helpers for checking the gpt status field. */ +static inline int +grub_gpt_mbr_valid (grub_gpt_t gpt) +{ + return ((gpt->status & GRUB_GPT_PROTECTIVE_MBR) || + (gpt->status & GRUB_GPT_HYBRID_MBR)); +} + +static inline int +grub_gpt_primary_valid (grub_gpt_t gpt) +{ + return ((gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) && + (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID)); +} + +static inline int +grub_gpt_backup_valid (grub_gpt_t gpt) +{ + return ((gpt->status & GRUB_GPT_BACKUP_HEADER_VALID) && + (gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID)); +} + +static inline int +grub_gpt_both_valid (grub_gpt_t gpt) +{ + return grub_gpt_primary_valid (gpt) && grub_gpt_backup_valid (gpt); +} + +/* Translate GPT sectors to GRUB's 512 byte block addresses. */ +static inline grub_disk_addr_t +grub_gpt_sector_to_addr (grub_gpt_t gpt, grub_uint64_t sector) +{ + return (sector << (gpt->log_sector_size - GRUB_DISK_SECTOR_BITS)); +} + +/* Allocates and fills new grub_gpt structure, free with grub_gpt_free. */ +grub_gpt_t grub_gpt_read (grub_disk_t disk); + +/* Helper for indexing into the entries table. + * Returns NULL when the end of the table has been reached. */ +struct grub_gpt_partentry * grub_gpt_get_partentry (grub_gpt_t gpt, + grub_uint32_t n); + +/* Sync and update primary and backup headers if either are invalid. */ +grub_err_t grub_gpt_repair (grub_disk_t disk, grub_gpt_t gpt); + +/* Recompute checksums and revalidate everything, must be called after + * modifying any GPT data. */ +grub_err_t grub_gpt_update (grub_gpt_t gpt); + +/* Write headers and entry tables back to disk. */ +grub_err_t grub_gpt_write (grub_disk_t disk, grub_gpt_t gpt); + +void grub_gpt_free (grub_gpt_t gpt); + +grub_err_t grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr); +grub_err_t grub_gpt_header_check (struct grub_gpt_header *gpt, + unsigned int log_sector_size); + + +/* Utilities for simple partition data lookups, usage is intended to + * be similar to fs->label and fs->uuid functions. */ + +/* Return the partition label of the device DEVICE in LABEL. + * The label is in a new buffer and should be freed by the caller. */ +grub_err_t grub_gpt_part_label (grub_device_t device, char **label); + +/* Return the partition uuid of the device DEVICE in UUID. + * The uuid is in a new buffer and should be freed by the caller. */ +grub_err_t grub_gpt_part_uuid (grub_device_t device, char **uuid); + +/* Return the disk uuid of the device DEVICE in UUID. + * The uuid is in a new buffer and should be freed by the caller. */ +grub_err_t grub_gpt_disk_uuid (grub_device_t device, char **uuid); #endif /* ! GRUB_GPT_PARTITION_HEADER */ diff --git a/include/grub/net.h b/include/grub/net.h index 4a9069a14..65d442537 100644 --- a/include/grub/net.h +++ b/include/grub/net.h @@ -276,6 +276,7 @@ typedef struct grub_net grub_fs_t fs; int eof; int stall; + int port; } *grub_net_t; extern grub_net_t (*EXPORT_VAR (grub_net_open)) (const char *name); diff --git a/include/grub/search.h b/include/grub/search.h index d80347df3..7f69d25d1 100644 --- a/include/grub/search.h +++ b/include/grub/search.h @@ -25,5 +25,11 @@ void grub_search_fs_uuid (const char *key, const char *var, int no_floppy, char **hints, unsigned nhints); void grub_search_label (const char *key, const char *var, int no_floppy, char **hints, unsigned nhints); +void grub_search_part_uuid (const char *key, const char *var, int no_floppy, + char **hints, unsigned nhints); +void grub_search_part_label (const char *key, const char *var, int no_floppy, + char **hints, unsigned nhints); +void grub_search_disk_uuid (const char *key, const char *var, int no_floppy, + char **hints, unsigned nhints); #endif diff --git a/include/grub/smbios.h b/include/grub/smbios.h new file mode 100644 index 000000000..4b82fa845 --- /dev/null +++ b/include/grub/smbios.h @@ -0,0 +1,68 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2015 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_SMBIOS_HEADER +#define GRUB_SMBIOS_HEADER 1 + +#include + +#define GRUB_SMBIOS_TYPE_END_OF_TABLE ((grub_uint8_t)127) + +struct grub_smbios_ieps +{ + grub_uint8_t anchor[5]; /* "_DMI_" */ + grub_uint8_t checksum; + grub_uint16_t table_length; + grub_uint32_t table_address; + grub_uint16_t structures; + grub_uint8_t revision; +} GRUB_PACKED; + +struct grub_smbios_eps +{ + grub_uint8_t anchor[4]; /* "_SM_" */ + grub_uint8_t checksum; + grub_uint8_t length; /* 0x1f */ + grub_uint8_t version_major; + grub_uint8_t version_minor; + grub_uint16_t maximum_structure_size; + grub_uint8_t revision; + grub_uint8_t formatted[5]; + struct grub_smbios_ieps intermediate; +} GRUB_PACKED; + +struct grub_smbios_eps3 +{ + grub_uint8_t anchor[5]; /* "_SM3_" */ + grub_uint8_t checksum; + grub_uint8_t length; /* 0x18 */ + grub_uint8_t version_major; + grub_uint8_t version_minor; + grub_uint8_t docrev; + grub_uint8_t revision; + grub_uint8_t reserved; + grub_uint32_t maximum_table_length; + grub_uint64_t table_address; +} GRUB_PACKED; + +struct grub_smbios_eps *grub_smbios_get_eps (void); +struct grub_smbios_eps3 *grub_smbios_get_eps3 (void); +struct grub_smbios_eps *grub_machine_smbios_get_eps (void); +struct grub_smbios_eps3 *grub_machine_smbios_get_eps3 (void); + +#endif /* ! GRUB_SMBIOS_HEADER */ diff --git a/include/grub/verity-hash.h b/include/grub/verity-hash.h new file mode 100644 index 000000000..448d9aff0 --- /dev/null +++ b/include/grub/verity-hash.h @@ -0,0 +1,51 @@ +/* CoreOS verity hash */ + +#define VERITY_ARG " verity.usrhash=" +#define VERITY_ARG_LENGTH (sizeof (VERITY_ARG) - 1) +#define VERITY_HASH_LENGTH 64 +#define VERITY_CMDLINE_LENGTH ((VERITY_ARG_LENGTH)+(VERITY_HASH_LENGTH)) + +#if defined(__aarch64__) +# define VERITY_HASH_OFFSET 512 +#elif defined(__i386__) || defined(__amd64__) +# define VERITY_HASH_OFFSET 0x40 +#else +# error Unsupported arch +#endif + + +/** + * grub_pass_verity_hash - Reads the CoreOS verity hash value from a well known + * kernel image offset and adds a kernel command line argument for it. + * + * @pImage: Kernel image buffer. + * @cmdline: Kernel command line buffer. + * @cmdline_max_len: Kernel command line buffer length. + */ + +static inline void grub_pass_verity_hash(const void *pImage, + char *cmdline, + grub_size_t cmdline_max_len) +{ + const char *buf = pImage; + grub_size_t cmdline_len; + int i; + + for (i=VERITY_HASH_OFFSET; i '9') // Not a number + if (buf[i] < 'a' || buf[i] > 'f') // Not a hex letter + return; + } + + cmdline_len = grub_strlen(cmdline); + if (cmdline_len + VERITY_CMDLINE_LENGTH > cmdline_max_len) + return; + + grub_memcpy (cmdline + cmdline_len, VERITY_ARG, VERITY_ARG_LENGTH); + cmdline_len += VERITY_ARG_LENGTH; + grub_memcpy (cmdline + cmdline_len, buf + VERITY_HASH_OFFSET, + VERITY_HASH_LENGTH); + cmdline_len += VERITY_HASH_LENGTH; + cmdline[cmdline_len] = '\0'; +} diff --git a/include/grub/xen_file.h b/include/grub/xen_file.h index 658799952..f8d8b19a7 100644 --- a/include/grub/xen_file.h +++ b/include/grub/xen_file.h @@ -24,6 +24,9 @@ #include grub_elf_t grub_xen_file (grub_file_t file); +grub_elf_t grub_xen_file_and_cmdline (grub_file_t file, + char *cmdline, + grub_size_t cmdline_max_len); struct grub_xen_file_info { diff --git a/tests/gpt_unit_test.c b/tests/gpt_unit_test.c new file mode 100644 index 000000000..218b18697 --- /dev/null +++ b/tests/gpt_unit_test.c @@ -0,0 +1,840 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2014 CoreOS, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* from gnulib */ +#include + +/* Confirm that the GPT structures conform to the sizes in the spec: + * The header size "must be greater than or equal to 92 and must be less + * than or equal to the logical block size." + * The partition entry size must be "a value of 128*(2^n) where n is an + * integer greater than or equal to zero (e.g., 128, 256, 512, etc.)." */ +verify (sizeof (struct grub_gpt_header) == 92); +verify (sizeof (struct grub_gpt_partentry) == 128); + +/* GPT section sizes. */ +#define HEADER_SIZE (sizeof (struct grub_gpt_header)) +#define HEADER_PAD (GRUB_DISK_SECTOR_SIZE - HEADER_SIZE) +#define ENTRY_SIZE (sizeof (struct grub_gpt_partentry)) +#define TABLE_ENTRIES 0x80 +#define TABLE_SIZE (TABLE_ENTRIES * ENTRY_SIZE) +#define TABLE_SECTORS (TABLE_SIZE / GRUB_DISK_SECTOR_SIZE) + +/* Double check that the table size calculation was valid. */ +verify (TABLE_SECTORS * GRUB_DISK_SECTOR_SIZE == TABLE_SIZE); + +/* GPT section locations for a 1MiB disk. */ +#define DISK_SECTORS 0x800 +#define DISK_SIZE (GRUB_DISK_SECTOR_SIZE * DISK_SECTORS) +#define PRIMARY_HEADER_SECTOR 0x1 +#define PRIMARY_TABLE_SECTOR 0x2 +#define BACKUP_HEADER_SECTOR (DISK_SECTORS - 0x1) +#define BACKUP_TABLE_SECTOR (BACKUP_HEADER_SECTOR - TABLE_SECTORS) + +#define DATA_START_SECTOR (PRIMARY_TABLE_SECTOR + TABLE_SECTORS) +#define DATA_END_SECTOR (BACKUP_TABLE_SECTOR - 0x1) +#define DATA_SECTORS (BACKUP_TABLE_SECTOR - DATA_START_SECTOR) +#define DATA_SIZE (GRUB_DISK_SECTOR_SIZE * DATA_SECTORS) + +struct test_disk +{ + struct grub_msdos_partition_mbr mbr; + + struct grub_gpt_header primary_header; + grub_uint8_t primary_header_pad[HEADER_PAD]; + struct grub_gpt_partentry primary_entries[TABLE_ENTRIES]; + + grub_uint8_t data[DATA_SIZE]; + + struct grub_gpt_partentry backup_entries[TABLE_ENTRIES]; + struct grub_gpt_header backup_header; + grub_uint8_t backup_header_pad[HEADER_PAD]; +} GRUB_PACKED; + +/* Sanity check that all the above ugly math was correct. */ +verify (sizeof (struct test_disk) == DISK_SIZE); + +struct test_data +{ + int fd; + grub_device_t dev; + struct test_disk *raw; +}; + + +/* Sample primary GPT header for a 1MB disk. */ +static const struct grub_gpt_header example_primary = { + .magic = GRUB_GPT_HEADER_MAGIC, + .version = GRUB_GPT_HEADER_VERSION, + .headersize = sizeof (struct grub_gpt_header), + .crc32 = grub_cpu_to_le32_compile_time (0xb985abe0), + .header_lba = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), + .alternate_lba = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), + .start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR), + .end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR), + .guid = GRUB_GPT_GUID_INIT(0x69c131ad, 0x67d6, 0x46c6, + 0x93, 0xc4, 0x12, 0x4c, 0x75, 0x52, 0x56, 0xac), + .partitions = grub_cpu_to_le64_compile_time (PRIMARY_TABLE_SECTOR), + .maxpart = grub_cpu_to_le32_compile_time (TABLE_ENTRIES), + .partentry_size = grub_cpu_to_le32_compile_time (ENTRY_SIZE), + .partentry_crc32 = grub_cpu_to_le32_compile_time (0x074e052c), +}; + +static const struct grub_gpt_partentry example_entries[TABLE_ENTRIES] = { + { + .type = GRUB_GPT_PARTITION_TYPE_EFI_SYSTEM, + .guid = GRUB_GPT_GUID_INIT (0xa0f1792e, 0xb4ce, 0x4136, 0xbc, 0xf2, + 0x1a, 0xfc, 0x13, 0x3c, 0x28, 0x28), + .start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR), + .end = grub_cpu_to_le64_compile_time (0x3f), + .attrib = 0x0, + .name = { + grub_cpu_to_le16_compile_time ('E'), + grub_cpu_to_le16_compile_time ('F'), + grub_cpu_to_le16_compile_time ('I'), + grub_cpu_to_le16_compile_time (' '), + grub_cpu_to_le16_compile_time ('S'), + grub_cpu_to_le16_compile_time ('Y'), + grub_cpu_to_le16_compile_time ('S'), + grub_cpu_to_le16_compile_time ('T'), + grub_cpu_to_le16_compile_time ('E'), + grub_cpu_to_le16_compile_time ('M'), + 0x0, + } + }, + { + .type = GRUB_GPT_PARTITION_TYPE_BIOS_BOOT, + .guid = GRUB_GPT_GUID_INIT (0x876c898d, 0x1b40, 0x4727, 0xa1, 0x61, + 0xed, 0xf9, 0xb5, 0x48, 0x66, 0x74), + .start = grub_cpu_to_le64_compile_time (0x40), + .end = grub_cpu_to_le64_compile_time (0x7f), + .attrib = grub_cpu_to_le64_compile_time ( + 1ULL << GRUB_GPT_PART_ATTR_OFFSET_LEGACY_BIOS_BOOTABLE), + .name = { + grub_cpu_to_le16_compile_time ('B'), + grub_cpu_to_le16_compile_time ('I'), + grub_cpu_to_le16_compile_time ('O'), + grub_cpu_to_le16_compile_time ('S'), + grub_cpu_to_le16_compile_time (' '), + grub_cpu_to_le16_compile_time ('B'), + grub_cpu_to_le16_compile_time ('O'), + grub_cpu_to_le16_compile_time ('O'), + grub_cpu_to_le16_compile_time ('T'), + 0x0, + } + }, +}; + +/* And the backup header. */ +static const struct grub_gpt_header example_backup = { + .magic = GRUB_GPT_HEADER_MAGIC, + .version = GRUB_GPT_HEADER_VERSION, + .headersize = sizeof (struct grub_gpt_header), + .crc32 = grub_cpu_to_le32_compile_time (0x0af785eb), + .header_lba = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), + .alternate_lba = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), + .start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR), + .end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR), + .guid = GRUB_GPT_GUID_INIT(0x69c131ad, 0x67d6, 0x46c6, + 0x93, 0xc4, 0x12, 0x4c, 0x75, 0x52, 0x56, 0xac), + .partitions = grub_cpu_to_le64_compile_time (BACKUP_TABLE_SECTOR), + .maxpart = grub_cpu_to_le32_compile_time (TABLE_ENTRIES), + .partentry_size = grub_cpu_to_le32_compile_time (ENTRY_SIZE), + .partentry_crc32 = grub_cpu_to_le32_compile_time (0x074e052c), +}; + +/* Sample protective MBR for the same 1MB disk. Note, this matches + * parted and fdisk behavior. The UEFI spec uses different values. */ +static const struct grub_msdos_partition_mbr example_pmbr = { + .entries = {{.flag = 0x00, + .start_head = 0x00, + .start_sector = 0x01, + .start_cylinder = 0x00, + .type = 0xee, + .end_head = 0xfe, + .end_sector = 0xff, + .end_cylinder = 0xff, + .start = grub_cpu_to_le32_compile_time (0x1), + .length = grub_cpu_to_le32_compile_time (DISK_SECTORS - 0x1), + }}, + .signature = grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE), +}; + +/* If errors are left in grub's error stack things can get confused. */ +static void +assert_error_stack_empty (void) +{ + do + { + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "error on stack: %s", grub_errmsg); + } + while (grub_error_pop ()); +} + +static grub_err_t +execute_command2 (const char *name, const char *arg1, const char *arg2) +{ + grub_command_t cmd; + grub_err_t err; + char *argv[2]; + + cmd = grub_command_find (name); + if (!cmd) + grub_fatal ("can't find command %s", name); + + argv[0] = strdup (arg1); + argv[1] = strdup (arg2); + err = (cmd->func) (cmd, 2, argv); + free (argv[0]); + free (argv[1]); + + return err; +} + +static void +sync_disk (struct test_data *data) +{ + if (msync (data->raw, DISK_SIZE, MS_SYNC | MS_INVALIDATE) < 0) + grub_fatal ("Syncing disk failed: %s", strerror (errno)); + + grub_disk_cache_invalidate_all (); +} + +static void +reset_disk (struct test_data *data) +{ + memset (data->raw, 0, DISK_SIZE); + + /* Initialize image with valid example tables. */ + memcpy (&data->raw->mbr, &example_pmbr, sizeof (data->raw->mbr)); + memcpy (&data->raw->primary_header, &example_primary, + sizeof (data->raw->primary_header)); + memcpy (&data->raw->primary_entries, &example_entries, + sizeof (data->raw->primary_entries)); + memcpy (&data->raw->backup_entries, &example_entries, + sizeof (data->raw->backup_entries)); + memcpy (&data->raw->backup_header, &example_backup, + sizeof (data->raw->backup_header)); + + sync_disk (data); +} + +static void +open_disk (struct test_data *data) +{ + const char *loop = "loop0"; + char template[] = "/tmp/grub_gpt_test.XXXXXX"; + char host[sizeof ("(host)") + sizeof (template)]; + + data->fd = mkstemp (template); + if (data->fd < 0) + grub_fatal ("Creating %s failed: %s", template, strerror (errno)); + + if (ftruncate (data->fd, DISK_SIZE) < 0) + { + int err = errno; + unlink (template); + grub_fatal ("Resizing %s failed: %s", template, strerror (err)); + } + + data->raw = mmap (NULL, DISK_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, data->fd, 0); + if (data->raw == MAP_FAILED) + { + int err = errno; + unlink (template); + grub_fatal ("Maping %s failed: %s", template, strerror (err)); + } + + snprintf (host, sizeof (host), "(host)%s", template); + if (execute_command2 ("loopback", loop, host) != GRUB_ERR_NONE) + { + unlink (template); + grub_fatal ("loopback %s %s failed: %s", loop, host, grub_errmsg); + } + + if (unlink (template) < 0) + grub_fatal ("Unlinking %s failed: %s", template, strerror (errno)); + + reset_disk (data); + + data->dev = grub_device_open (loop); + if (!data->dev) + grub_fatal ("Opening %s failed: %s", loop, grub_errmsg); +} + +static void +close_disk (struct test_data *data) +{ + char *loop; + + assert_error_stack_empty (); + + if (munmap (data->raw, DISK_SIZE) || close (data->fd)) + grub_fatal ("Closing disk image failed: %s", strerror (errno)); + + loop = strdup (data->dev->disk->name); + grub_test_assert (grub_device_close (data->dev) == GRUB_ERR_NONE, + "Closing disk device failed: %s", grub_errmsg); + + grub_test_assert (execute_command2 ("loopback", "-d", loop) == + GRUB_ERR_NONE, "loopback -d %s failed: %s", loop, + grub_errmsg); + + free (loop); +} + +static grub_gpt_t +read_disk (struct test_data *data) +{ + grub_gpt_t gpt; + + gpt = grub_gpt_read (data->dev->disk); + if (gpt == NULL) + grub_fatal ("grub_gpt_read failed: %s", grub_errmsg); + + return gpt; +} + +static void +pmbr_test (void) +{ + struct grub_msdos_partition_mbr mbr; + + memset (&mbr, 0, sizeof (mbr)); + + /* Empty is invalid. */ + grub_gpt_pmbr_check (&mbr); + grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + /* A table without a protective partition is invalid. */ + mbr.signature = grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE); + grub_gpt_pmbr_check (&mbr); + grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + /* A table with a protective type is ok. */ + memcpy (&mbr, &example_pmbr, sizeof (mbr)); + grub_gpt_pmbr_check (&mbr); + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; +} + +static void +header_test (void) +{ + struct grub_gpt_header primary, backup; + + /* Example headers should be valid. */ + memcpy (&primary, &example_primary, sizeof (primary)); + grub_gpt_header_check (&primary, GRUB_DISK_SECTOR_BITS); + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + memcpy (&backup, &example_backup, sizeof (backup)); + grub_gpt_header_check (&backup, GRUB_DISK_SECTOR_BITS); + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + /* Twiddle the GUID to invalidate the CRC. */ + primary.guid.data1 = 0; + grub_gpt_header_check (&primary, GRUB_DISK_SECTOR_BITS); + grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + backup.guid.data1 = 0; + grub_gpt_header_check (&backup, GRUB_DISK_SECTOR_BITS); + grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; +} + +static void +read_valid_test (void) +{ + struct test_data data; + grub_gpt_t gpt; + + open_disk (&data); + gpt = read_disk (&data); + grub_test_assert (gpt->status == (GRUB_GPT_PROTECTIVE_MBR | + GRUB_GPT_PRIMARY_HEADER_VALID | + GRUB_GPT_PRIMARY_ENTRIES_VALID | + GRUB_GPT_BACKUP_HEADER_VALID | + GRUB_GPT_BACKUP_ENTRIES_VALID), + "unexpected status: 0x%02x", gpt->status); + grub_gpt_free (gpt); + close_disk (&data); +} + +static void +read_invalid_entries_test (void) +{ + struct test_data data; + grub_gpt_t gpt; + + open_disk (&data); + + /* Corrupt the first entry in both tables. */ + memset (&data.raw->primary_entries[0], 0x55, + sizeof (data.raw->primary_entries[0])); + memset (&data.raw->backup_entries[0], 0x55, + sizeof (data.raw->backup_entries[0])); + sync_disk (&data); + + gpt = grub_gpt_read (data.dev->disk); + grub_test_assert (gpt == NULL, "no error reported for corrupt entries"); + grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + close_disk (&data); +} + +static void +read_fallback_test (void) +{ + struct test_data data; + grub_gpt_t gpt; + + open_disk (&data); + + /* Corrupt the primary header. */ + memset (&data.raw->primary_header.guid, 0x55, + sizeof (data.raw->primary_header.guid)); + sync_disk (&data); + gpt = read_disk (&data); + grub_test_assert ((gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) == 0, + "unreported corrupt primary header"); + grub_gpt_free (gpt); + reset_disk (&data); + + /* Corrupt the backup header. */ + memset (&data.raw->backup_header.guid, 0x55, + sizeof (data.raw->backup_header.guid)); + sync_disk (&data); + gpt = read_disk (&data); + grub_test_assert ((gpt->status & GRUB_GPT_BACKUP_HEADER_VALID) == 0, + "unreported corrupt backup header"); + grub_gpt_free (gpt); + reset_disk (&data); + + /* Corrupt the primary entry table. */ + memset (&data.raw->primary_entries[0], 0x55, + sizeof (data.raw->primary_entries[0])); + sync_disk (&data); + gpt = read_disk (&data); + grub_test_assert ((gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID) == 0, + "unreported corrupt primary entries table"); + grub_gpt_free (gpt); + reset_disk (&data); + + /* Corrupt the backup entry table. */ + memset (&data.raw->backup_entries[0], 0x55, + sizeof (data.raw->backup_entries[0])); + sync_disk (&data); + gpt = read_disk (&data); + grub_test_assert ((gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID) == 0, + "unreported corrupt backup entries table"); + grub_gpt_free (gpt); + reset_disk (&data); + + /* If primary is corrupt and disk size is unknown fallback fails. */ + memset (&data.raw->primary_header.guid, 0x55, + sizeof (data.raw->primary_header.guid)); + sync_disk (&data); + data.dev->disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN; + gpt = grub_gpt_read (data.dev->disk); + grub_test_assert (gpt == NULL, "no error reported"); + grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + close_disk (&data); +} + +static void +repair_test (void) +{ + struct test_data data; + grub_gpt_t gpt; + + open_disk (&data); + + /* Erase/Repair primary. */ + memset (&data.raw->primary_header, 0, sizeof (data.raw->primary_header)); + sync_disk (&data); + gpt = read_disk (&data); + grub_gpt_repair (data.dev->disk, gpt); + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "repair failed: %s", grub_errmsg); + if (memcmp (&gpt->primary, &example_primary, sizeof (gpt->primary))) + { + printf ("Invalid restored primary header:\n"); + hexdump (16, (char*)&gpt->primary, sizeof (gpt->primary)); + printf ("Expected primary header:\n"); + hexdump (16, (char*)&example_primary, sizeof (example_primary)); + grub_test_assert (0, "repair did not restore primary header"); + } + grub_gpt_free (gpt); + reset_disk (&data); + + /* Erase/Repair backup. */ + memset (&data.raw->backup_header, 0, sizeof (data.raw->backup_header)); + sync_disk (&data); + gpt = read_disk (&data); + grub_gpt_repair (data.dev->disk, gpt); + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "repair failed: %s", grub_errmsg); + if (memcmp (&gpt->backup, &example_backup, sizeof (gpt->backup))) + { + printf ("Invalid restored backup header:\n"); + hexdump (16, (char*)&gpt->backup, sizeof (gpt->backup)); + printf ("Expected backup header:\n"); + hexdump (16, (char*)&example_backup, sizeof (example_backup)); + grub_test_assert (0, "repair did not restore backup header"); + } + grub_gpt_free (gpt); + reset_disk (&data); + + close_disk (&data); +} + +/* Finding/reading/writing the backup GPT may be difficult if the OS and + * BIOS report different sizes for the same disk. We need to gracefully + * recognize this and avoid causing trouble for the OS. */ +static void +weird_disk_size_test (void) +{ + struct test_data data; + grub_gpt_t gpt; + + open_disk (&data); + + /* Chop off 65536 bytes (128 512B sectors) which may happen when the + * BIOS thinks you are using a software RAID system that reserves that + * area for metadata when in fact you are not and using the bare disk. */ + grub_test_assert(data.dev->disk->total_sectors == DISK_SECTORS, + "unexpected disk size: 0x%llx", + (unsigned long long) data.dev->disk->total_sectors); + data.dev->disk->total_sectors -= 128; + + gpt = read_disk (&data); + assert_error_stack_empty (); + /* Reading the alternate_lba should have been blocked and reading + * the (new) end of disk should have found no useful data. */ + grub_test_assert ((gpt->status & GRUB_GPT_BACKUP_HEADER_VALID) == 0, + "unreported missing backup header"); + + /* We should be able to reconstruct the backup header and the location + * of the backup should remain unchanged, trusting the GPT data over + * what the BIOS is telling us. Further changes are left to the OS. */ + grub_gpt_repair (data.dev->disk, gpt); + grub_test_assert (grub_errno == GRUB_ERR_NONE, + "repair failed: %s", grub_errmsg); + grub_test_assert (memcmp (&gpt->primary, &example_primary, + sizeof (gpt->primary)) == 0, + "repair corrupted primary header"); + + grub_gpt_free (gpt); + close_disk (&data); +} + +static void +iterate_partitions_test (void) +{ + struct test_data data; + struct grub_gpt_partentry *p; + grub_gpt_t gpt; + grub_uint32_t n; + + open_disk (&data); + gpt = read_disk (&data); + + for (n = 0; (p = grub_gpt_get_partentry (gpt, n)) != NULL; n++) + grub_test_assert (memcmp (p, &example_entries[n], sizeof (*p)) == 0, + "unexpected partition %d data", n); + + grub_test_assert (n == TABLE_ENTRIES, "unexpected partition limit: %d", n); + + grub_gpt_free (gpt); + close_disk (&data); +} + +static void +large_partitions_test (void) +{ + struct test_data data; + struct grub_gpt_partentry *p; + grub_gpt_t gpt; + grub_uint32_t n; + + open_disk (&data); + + /* Double the entry size, cut the number of entries in half. */ + data.raw->primary_header.maxpart = + data.raw->backup_header.maxpart = + grub_cpu_to_le32_compile_time (TABLE_ENTRIES/2); + data.raw->primary_header.partentry_size = + data.raw->backup_header.partentry_size = + grub_cpu_to_le32_compile_time (ENTRY_SIZE*2); + data.raw->primary_header.partentry_crc32 = + data.raw->backup_header.partentry_crc32 = + grub_cpu_to_le32_compile_time (0xf2c45af8); + data.raw->primary_header.crc32 = grub_cpu_to_le32_compile_time (0xde00cc8f); + data.raw->backup_header.crc32 = grub_cpu_to_le32_compile_time (0x6d72e284); + + memset (&data.raw->primary_entries, 0, + sizeof (data.raw->primary_entries)); + for (n = 0; n < TABLE_ENTRIES/2; n++) + memcpy (&data.raw->primary_entries[n*2], &example_entries[n], + sizeof (data.raw->primary_entries[0])); + memcpy (&data.raw->backup_entries, &data.raw->primary_entries, + sizeof (data.raw->backup_entries)); + + sync_disk(&data); + gpt = read_disk (&data); + + for (n = 0; (p = grub_gpt_get_partentry (gpt, n)) != NULL; n++) + grub_test_assert (memcmp (p, &example_entries[n], sizeof (*p)) == 0, + "unexpected partition %d data", n); + + grub_test_assert (n == TABLE_ENTRIES/2, "unexpected partition limit: %d", n); + + grub_gpt_free (gpt); + + /* Editing memory beyond the entry structure should still change the crc. */ + data.raw->primary_entries[1].attrib = 0xff; + + sync_disk(&data); + gpt = read_disk (&data); + grub_test_assert (gpt->status == (GRUB_GPT_PROTECTIVE_MBR | + GRUB_GPT_PRIMARY_HEADER_VALID | + GRUB_GPT_BACKUP_HEADER_VALID | + GRUB_GPT_BACKUP_ENTRIES_VALID), + "unexpected status: 0x%02x", gpt->status); + grub_gpt_free (gpt); + + close_disk (&data); +} + +static void +invalid_partsize_test (void) +{ + struct grub_gpt_header header = { + .magic = GRUB_GPT_HEADER_MAGIC, + .version = GRUB_GPT_HEADER_VERSION, + .headersize = sizeof (struct grub_gpt_header), + .crc32 = grub_cpu_to_le32_compile_time (0x1ff2a054), + .header_lba = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR), + .alternate_lba = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR), + .start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR), + .end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR), + .guid = GRUB_GPT_GUID_INIT(0x69c131ad, 0x67d6, 0x46c6, + 0x93, 0xc4, 0x12, 0x4c, 0x75, 0x52, 0x56, 0xac), + .partitions = grub_cpu_to_le64_compile_time (PRIMARY_TABLE_SECTOR), + .maxpart = grub_cpu_to_le32_compile_time (TABLE_ENTRIES), + /* Triple the entry size, which is not valid. */ + .partentry_size = grub_cpu_to_le32_compile_time (ENTRY_SIZE*3), + .partentry_crc32 = grub_cpu_to_le32_compile_time (0x074e052c), + }; + + grub_gpt_header_check(&header, GRUB_DISK_SECTOR_BITS); + grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE, + "unexpected error: %s", grub_errmsg); + grub_test_assert (strcmp(grub_errmsg, "invalid GPT entry size") == 0, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; +} + +static void +search_part_label_test (void) +{ + struct test_data data; + const char *test_result; + char *expected_result; + + open_disk (&data); + + expected_result = grub_xasprintf ("%s,gpt1", data.dev->disk->name); + grub_env_unset ("test_result"); + grub_search_part_label ("EFI SYSTEM", "test_result", 0, NULL, 0); + test_result = grub_env_get ("test_result"); + grub_test_assert (test_result && strcmp (test_result, expected_result) == 0, + "wrong device: %s (%s)", test_result, expected_result); + grub_free (expected_result); + + expected_result = grub_xasprintf ("%s,gpt2", data.dev->disk->name); + grub_env_unset ("test_result"); + grub_search_part_label ("BIOS BOOT", "test_result", 0, NULL, 0); + test_result = grub_env_get ("test_result"); + grub_test_assert (test_result && strcmp (test_result, expected_result) == 0, + "wrong device: %s (%s)", test_result, expected_result); + grub_free (expected_result); + + grub_env_unset ("test_result"); + grub_search_part_label ("bogus name", "test_result", 0, NULL, 0); + test_result = grub_env_get ("test_result"); + grub_test_assert (test_result == NULL, + "unexpected device: %s", test_result); + grub_test_assert (grub_errno == GRUB_ERR_FILE_NOT_FOUND, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + close_disk (&data); +} + +static void +search_part_uuid_test (void) +{ + struct test_data data; + const char gpt1_uuid[] = "A0F1792E-B4CE-4136-BCF2-1AFC133C2828"; + const char gpt2_uuid[] = "876c898d-1b40-4727-a161-edf9b5486674"; + const char bogus_uuid[] = "1534c928-c50e-4866-9daf-6a9fd7918a76"; + const char *test_result; + char *expected_result; + + open_disk (&data); + + expected_result = grub_xasprintf ("%s,gpt1", data.dev->disk->name); + grub_env_unset ("test_result"); + grub_search_part_uuid (gpt1_uuid, "test_result", 0, NULL, 0); + test_result = grub_env_get ("test_result"); + grub_test_assert (test_result && strcmp (test_result, expected_result) == 0, + "wrong device: %s (%s)", test_result, expected_result); + grub_free (expected_result); + + expected_result = grub_xasprintf ("%s,gpt2", data.dev->disk->name); + grub_env_unset ("test_result"); + grub_search_part_uuid (gpt2_uuid, "test_result", 0, NULL, 0); + test_result = grub_env_get ("test_result"); + grub_test_assert (test_result && strcmp (test_result, expected_result) == 0, + "wrong device: %s (%s)", test_result, expected_result); + grub_free (expected_result); + + grub_env_unset ("test_result"); + grub_search_part_uuid (bogus_uuid, "test_result", 0, NULL, 0); + test_result = grub_env_get ("test_result"); + grub_test_assert (test_result == NULL, + "unexpected device: %s", test_result); + grub_test_assert (grub_errno == GRUB_ERR_FILE_NOT_FOUND, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + close_disk (&data); +} + +static void +search_disk_uuid_test (void) +{ + struct test_data data; + const char disk_uuid[] = "69c131ad-67d6-46c6-93c4-124c755256ac"; + const char bogus_uuid[] = "1534c928-c50e-4866-9daf-6a9fd7918a76"; + const char *test_result; + char *expected_result; + + open_disk (&data); + + expected_result = grub_xasprintf ("%s", data.dev->disk->name); + grub_env_unset ("test_result"); + grub_search_disk_uuid (disk_uuid, "test_result", 0, NULL, 0); + test_result = grub_env_get ("test_result"); + grub_test_assert (test_result && strcmp (test_result, expected_result) == 0, + "wrong device: %s (%s)", test_result, expected_result); + grub_free (expected_result); + + grub_env_unset ("test_result"); + grub_search_disk_uuid (bogus_uuid, "test_result", 0, NULL, 0); + test_result = grub_env_get ("test_result"); + grub_test_assert (test_result == NULL, + "unexpected device: %s", test_result); + grub_test_assert (grub_errno == GRUB_ERR_FILE_NOT_FOUND, + "unexpected error: %s", grub_errmsg); + grub_errno = GRUB_ERR_NONE; + + close_disk (&data); +} + +void +grub_unit_test_init (void) +{ + grub_init_all (); + grub_hostfs_init (); + grub_host_init (); + grub_test_register ("gpt_pmbr_test", pmbr_test); + grub_test_register ("gpt_header_test", header_test); + grub_test_register ("gpt_read_valid_test", read_valid_test); + grub_test_register ("gpt_read_invalid_test", read_invalid_entries_test); + grub_test_register ("gpt_read_fallback_test", read_fallback_test); + grub_test_register ("gpt_repair_test", repair_test); + grub_test_register ("gpt_iterate_partitions_test", iterate_partitions_test); + grub_test_register ("gpt_large_partitions_test", large_partitions_test); + grub_test_register ("gpt_invalid_partsize_test", invalid_partsize_test); + grub_test_register ("gpt_weird_disk_size_test", weird_disk_size_test); + grub_test_register ("gpt_search_part_label_test", search_part_label_test); + grub_test_register ("gpt_search_uuid_test", search_part_uuid_test); + grub_test_register ("gpt_search_disk_uuid_test", search_disk_uuid_test); +} + +void +grub_unit_test_fini (void) +{ + grub_test_unregister ("gpt_pmbr_test"); + grub_test_unregister ("gpt_header_test"); + grub_test_unregister ("gpt_read_valid_test"); + grub_test_unregister ("gpt_read_invalid_test"); + grub_test_unregister ("gpt_read_fallback_test"); + grub_test_unregister ("gpt_repair_test"); + grub_test_unregister ("gpt_iterate_partitions_test"); + grub_test_unregister ("gpt_large_partitions_test"); + grub_test_unregister ("gpt_invalid_partsize_test"); + grub_test_unregister ("gpt_weird_disk_size_test"); + grub_test_unregister ("gpt_search_part_label_test"); + grub_test_unregister ("gpt_search_part_uuid_test"); + grub_test_unregister ("gpt_search_disk_uuid_test"); + grub_fini_all (); +} diff --git a/tests/gptprio_test.in b/tests/gptprio_test.in new file mode 100644 index 000000000..c5cf0f3b7 --- /dev/null +++ b/tests/gptprio_test.in @@ -0,0 +1,207 @@ +#! /bin/bash +set -e + +# Copyright (C) 2010 Free Software Foundation, Inc. +# Copyright (C) 2014 CoreOS, Inc. +# +# GRUB is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# GRUB is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GRUB. If not, see . + +sgdisk=sgdisk +grubshell=@builddir@/grub-shell + +if ! which "${sgdisk}" >/dev/null 2>&1; then + echo "sgdisk not installed; cannot test gptprio." + exit 77 +fi + +. "@builddir@/grub-core/modinfo.sh" + +case "${grub_modinfo_target_cpu}-${grub_modinfo_platform}" in + mips-qemu_mips | mipsel-qemu_mips | i386-qemu | i386-multiboot | i386-coreboot | mipsel-loongson) + disk=ata0 + ;; + powerpc-ieee1275) + disk=ieee1275//pci@80000000/mac-io@4/ata-3@20000/disk@0 + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + sparc64-ieee1275) + disk=ieee1275//pci@1fe\,0/pci-ata@5/ide0@500/disk@0 + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + i386-ieee1275) + disk=ieee1275/d + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + mips-arc) + # FIXME: ARC firmware has bugs which prevent it from accessing hard disk w/o dvh disklabel. + exit 0 ;; + mipsel-arc) + disk=arc/scsi0/disk0/rdisk0 + ;; + *) + disk=hd0 + ;; +esac +img1="`mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX"`" || exit 1 +trap "rm -f '${img1}'" EXIT + +prio_type="5dfbf5f4-2848-4bac-aa5e-0d9a20b745a6" +declare -a prio_uuid +prio_uuid[2]="9b003904-d006-4ab3-97f1-73f547b7af1a" +prio_uuid[3]="1aa5a658-5b02-414d-9b71-f7e6c151f0cd" +prio_uuid[4]="8aa0240d-98af-42b0-b32a-ccbe0572d62b" + +create_disk_image () { + size=$1 + rm -f "${img1}" + dd if=/dev/zero of="${img1}" bs=512 count=1 seek=$((size - 1)) status=none + ${sgdisk} \ + -n 1:0:+1 -c 1:ESP -t 1:ef00 \ + -n 2:0:+1 -c 2:A -t 2:"${prio_type}" -u 2:"${prio_uuid[2]}" \ + -n 3:0:+1 -c 3:B -t 3:"${prio_type}" -u 3:"${prio_uuid[3]}" \ + -n 4:0:+1 -c 4:C -t 4:"${prio_type}" -u 4:"${prio_uuid[4]}" \ + "${img1}" >/dev/null +} + +wipe_disk_area () { + sector=$1 + size=$2 + dd if=/dev/zero of="${img1}" bs=512 count=${size} seek=${sector} conv=notrunc status=none +} + +is_zero () { + sector=$1 + size=$2 + cmp -s -i $((sector * 512)) -n $((size * 512)) /dev/zero "${img1}" +} + +check_is_zero () { + sector=$1 + size=$2 + if ! is_zero "$@"; then + echo "$size sector(s) starting at $sector should be all zero" + exit 1 + fi +} + +check_not_zero () { + sector=$1 + size=$2 + if is_zero "$@"; then + echo "$size sector(s) starting at $sector should not be all zero" + exit 1 + fi +} + +fmt_prio () { + priority=$(( ( $1 & 15 ) << 48 )) + tries=$(( ( $2 & 15 ) << 52 )) + success=$(( ( $3 & 1 ) << 56 )) + printf %016x $(( priority | tries | success )) +} + +set_prio () { + part="$1" + attr=$(fmt_prio $2 $3 $4) + ${sgdisk} -A "${part}:=:${attr}" "${img1}" >/dev/null +} + +check_prio () { + part="$1" + expect=$(fmt_prio $2 $3 $4) + result=$(LANG=C ${sgdisk} -i "${part}" "${img1}" 2>&1 \ + | awk '/^Attribute flags: / {print $3}') + if [[ "${expect}" != "${result}" ]]; then + echo "Partition ${part} has attributes ${result:-??}, not ${expect}" + exit 1 + fi +} + +run_next() { + "${grubshell}" --disk="${img1}" --modules=gptprio <. + +parted=parted +grubshell=@builddir@/grub-shell + +. "@builddir@/grub-core/modinfo.sh" + +case "${grub_modinfo_target_cpu}-${grub_modinfo_platform}" in + mips-qemu_mips | mipsel-qemu_mips | i386-qemu | i386-multiboot | i386-coreboot | mipsel-loongson) + disk=ata0 + ;; + powerpc-ieee1275) + disk=ieee1275//pci@80000000/mac-io@4/ata-3@20000/disk@0 + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + sparc64-ieee1275) + disk=ieee1275//pci@1fe\,0/pci-ata@5/ide0@500/disk@0 + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + i386-ieee1275) + disk=ieee1275/d + # FIXME: QEMU firmware has bugs which prevent it from accessing hard disk w/o recognised label. + exit 0 + ;; + mips-arc) + # FIXME: ARC firmware has bugs which prevent it from accessing hard disk w/o dvh disklabel. + exit 0 ;; + mipsel-arc) + disk=arc/scsi0/disk0/rdisk0 + ;; + *) + disk=hd0 + ;; +esac +img1="`mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX"`" || exit 1 +img2="`mktemp "${TMPDIR:-/tmp}/tmp.XXXXXXXXXX"`" || exit 1 +trap "rm -f '${img1}' '${img2}'" EXIT + +create_disk_image () { + size=$1 + rm -f "${img1}" + dd if=/dev/zero of="${img1}" bs=512 count=1 seek=$((size - 1)) status=none + ${parted} -a none -s "${img1}" mklabel gpt + cp "${img1}" "${img2}" +} + +wipe_disk_area () { + sector=$1 + size=$2 + dd if=/dev/zero of="${img2}" bs=512 count=${size} seek=${sector} conv=notrunc status=none +} + +do_repair () { + output="`echo "gptrepair ($disk)" | "${grubshell}" --disk="${img2}"`" + if echo "${output}" | grep ^error; then + return 1 + fi + if echo "${output}" | grep -v GPT; then + echo "Unexpected output ${output}" + return 1 + fi + echo "${output}" +} + +echo "Nothing to repair:" +create_disk_image 100 +do_repair +cmp "${img1}" "${img2}" +echo + +echo "Repair primary (MBR left intact)" +create_disk_image 100 +wipe_disk_area 1 1 +do_repair +cmp "${img1}" "${img2}" +echo + +echo "Repair backup" +create_disk_image 100 +wipe_disk_area 99 1 +do_repair +cmp "${img1}" "${img2}" +echo diff --git a/tests/util/grub-fs-tester.in b/tests/util/grub-fs-tester.in index bc14a05ca..147d946d2 100644 --- a/tests/util/grub-fs-tester.in +++ b/tests/util/grub-fs-tester.in @@ -600,7 +600,7 @@ for LOGSECSIZE in $(range "$MINLOGSECSIZE" "$MAXLOGSECSIZE" 1); do GENERATED=n LODEVICES= MOUNTDEVICE= - + case x"$fs" in x"tarfs" | x"cpio_"*| x"ziso9660" | x"romfs" | x"squash4_"*\ | x"iso9660" | xjoliet | xrockridge | xrockridge_joliet \