On Wed, May 21, 2025 at 12:51:26PM +0000, Alec Brown wrote: > A Unified Kernel Image is a single UEFI PE file that combines a UEFI boot > stub, > a Linux kernel image, an initrd, and further resources. The uki command will > locate where the UKI file is and create a GRUB menu entry to load it. > > The Unified Kernel Image Specification: > https://uapi-group.org/specifications/specs/unified_kernel_image/ > > Signed-off-by: Alec Brown <alec.r.br...@oracle.com> > --- > docs/grub.texi | 33 +++ > grub-core/commands/blsuki.c | 463 +++++++++++++++++++++++++++++++++--- > include/grub/menu.h | 2 + > 3 files changed, 463 insertions(+), 35 deletions(-) > > diff --git a/docs/grub.texi b/docs/grub.texi > index adab93668..9a63129c7 100644 > --- a/docs/grub.texi > +++ b/docs/grub.texi > @@ -6491,6 +6491,7 @@ you forget a command, you can run the command > @command{help} > * tpm2_key_protector_clear:: Clear the TPM2 key protector > * true:: Do nothing, successfully > * trust:: Add public key to list of trusted keys > +* uki:: Load Unified Kernel Image menu entries > * unset:: Unset an environment variable > @comment * vbeinfo:: List available video modes > * verify_detached:: Verify detached digital signature > @@ -8184,6 +8185,38 @@ Unset the environment variable @var{envvar}. > @end deffn > > > +@node uki > +@subsection uki > + > +@deffn Command uki [@option{-p|--path} dir] [@option{-f|--enable-fallback}] > [@option{-d|--show-default}] [@option{-n|--show-non-default}] > [@option{-e|--entry} file] > +Load Unified Kernel Image (UKI) entries into the GRUB menu. Boot entries > +generated from @command{uki} won't interfere with entries from > @file{grub.cfg} appearing in the > +GRUB menu. Also, entries generated from @command{uki} only generate in > memory and don't
s/only generate/exist only/ > +update @file{grub.cfg}. > + > +The @option{--path} option overrides the default path to the directory > containing > +the UKI entries. If this option isn't used, the default location is > +/EFI/Linux in the EFI system partition. If no UKI entries are found, the > +@option{--enable-fallback} option can be used to check for entries in the > default > +directory. What is "the default directory"? > +The @option{--show-default} option allows the default boot entry to be added > to the > +GRUB menu from the UKI entries. > + > +The @option{--show-non-default} option allows non-default boot entries to be > added to > +the GRUB menu from the UKI entries. > + > +The @option{--entry} option allows specific boot entries to be added to the > GRUB menu > +from the UKI entries. > + > +The @option{--entry}, @option{--show-default}, and > @option{--show-non-default} options > +are used to filter which UKI entries are added to the GRUB menu. If none are > +used, all entries in the default location or the location specified by > @option{--path} > +will be added to the GRUB menu. > + > +References: > @uref{https://uapi-group.org/specifications/specs/unified_kernel_image/, The > Unified Kernel Image Specification} > +@end deffn > + > @ignore > @node vbeinfo > @subsection vbeinfo > diff --git a/grub-core/commands/blsuki.c b/grub-core/commands/blsuki.c > index bf2edc5ac..3f067281d 100644 > --- a/grub-core/commands/blsuki.c > +++ b/grub-core/commands/blsuki.c > @@ -32,6 +32,12 @@ > #include <grub/vercmp.h> > #include <grub/lib/envblk.h> > > +#ifdef GRUB_MACHINE_EFI > +#include <grub/efi/efi.h> > +#include <grub/efi/disk.h> > +#include <grub/efi/pe32.h> > +#endif > + > #ifdef GRUB_MACHINE_EMU > #include <grub/emu/misc.h> > #define GRUB_BOOT_DEVICE "/boot" > @@ -42,6 +48,13 @@ > GRUB_MOD_LICENSE ("GPLv3+"); > > #define GRUB_BLS_CONFIG_PATH "/loader/entries/" > +#define GRUB_UKI_CONFIG_PATH "/EFI/Linux" > + > +enum > + { > + BLSUKI_BLS_CMD, > + BLSUKI_UKI_CMD, > + }; > > static const struct grub_arg_option bls_opt[] = > { > @@ -53,6 +66,18 @@ static const struct grub_arg_option bls_opt[] = > {0, 0, 0, 0, 0, 0} > }; > > +#ifdef GRUB_MACHINE_EFI > +static const struct grub_arg_option uki_opt[] = > + { > + {"path", 'p', 0, N_("Specify path to find UKI entries."), N_("DIR"), > ARG_TYPE_PATHNAME}, > + {"enable-fallback", 'f', 0, "Fallback to the default BLS path if --path > fails to find UKI entries.", 0, ARG_TYPE_NONE}, > + {"show-default", 'd', 0, N_("Allow the default UKI entry to be added to > the GRUB menu."), 0, ARG_TYPE_NONE}, > + {"show-non-default", 'n', 0, N_("Allow the non-default UKI entries to be > added to the GRUB menu."), 0, ARG_TYPE_NONE}, > + {"entry", 'e', 0, N_("Allow specificUKII entries to be added to the GRUB > menu."), N_("FILE"), ARG_TYPE_FILE}, > + {0, 0, 0, 0, 0, 0} > + }; > +#endif > + > struct keyval > { > const char *key; > @@ -162,7 +187,7 @@ blsuki_add_keyval (grub_blsuki_entry_t *entry, char *key, > char *val) > * Find the value of the key named by keyname. If there are allowed to be > * more than one, pass a pointer to an int set to -1 the first time, and pass > * the same pointer through each time after, and it'll return them in sorted > - * order as defined in the BLS fragment file. > + * order. > */ > static char * > blsuki_get_val (grub_blsuki_entry_t *entry, const char *keyname, int *last) > @@ -304,27 +329,243 @@ bls_parse_keyvals (grub_file_t f, grub_blsuki_entry_t > *entry) > return err; > } > > +/* > + * This function searches for the .cmdline, .osrel, and .linux sections of a > + * UKI. We only need to store the data for the .cmdline and .osrel sections, > + * but we also need to verify that the .linux section exists. > + */ > +#ifdef GRUB_MACHINE_EFI > +static grub_err_t > +uki_parse_keyvals (grub_file_t f, grub_blsuki_entry_t *entry) > +{ > + struct grub_msdos_image_header *dos = NULL; > + struct grub_pe_image_header *pe = NULL; > + grub_off_t section_offset = 0; > + struct grub_pe32_section_table *section = NULL; > + struct grub_pe32_coff_header *coff_header = NULL; > + char *val = NULL; > + char *key = NULL; > + const char *target[] = {".cmdline", ".osrel", ".linux", NULL}; > + bool has_linux = false; > + grub_err_t err = GRUB_ERR_NONE; > + > + dos = grub_zalloc (sizeof (*dos)); > + if (dos == NULL) > + return grub_errno; > + if (grub_file_read (f, dos, sizeof (*dos)) < (grub_ssize_t) sizeof (*dos)) > + { > + err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read UKI image > header"); > + goto finish; > + } > + if (dos->msdos_magic != GRUB_PE32_MAGIC) > + { > + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("plain image kernel not > supported")); I would drop N_() from here. And s/kernel not/kernel is not/... > + goto finish; > + } > + > + grub_dprintf ("blsuki", "PE/COFF header @ %08x\n", > dos->pe_image_header_offset); > + pe = grub_zalloc (sizeof (*pe)); > + if (pe == NULL) > + { > + err = grub_errno; > + goto finish; > + } > + if (grub_file_seek (f, dos->pe_image_header_offset) == (grub_off_t) -1 || > + grub_file_read (f, pe, sizeof (*pe)) != sizeof (*pe)) > + { > + err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read COFF image > header"); > + goto finish; > + } > + if (pe->optional_header.magic != GRUB_PE32_NATIVE_MAGIC) > + { > + err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "non-native image not > supported"); > + goto finish; > + } > + > + coff_header = &(pe->coff_header); > + section_offset = dos->pe_image_header_offset + sizeof (*pe); > + > + for (int i = 0; i < coff_header->num_sections; i++) > + { > + key = NULL; > + val = NULL; > + section = grub_zalloc (sizeof (*section)); > + if (section == NULL) > + { > + err = grub_errno; > + goto finish; > + } > + > + if (grub_file_seek (f, section_offset) == (grub_off_t) -1 || > + grub_file_read (f, section, sizeof (*section)) != sizeof > (*section)) > + { > + err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read section > header"); > + goto finish; > + } > + > + key = grub_strndup (section->name, 8); > + if (key == NULL) > + { > + err = grub_errno; > + goto finish; > + } > + > + for (int j = 0; target[j] != NULL; j++) > + { > + if (grub_strcmp (key, target[j]) == 0) > + { > + /* > + * We don't need to read the contents of the .linux PE section, > but we > + * should verify that the section exists. > + */ > + if (grub_strcmp (key, ".linux") == 0) > + { > + has_linux = true; > + break; > + } > + > + val = grub_zalloc (section->raw_data_size); You blindly assume it is valid section size. Probably we should enforce sensible limits here... > + if (val == NULL) > + { > + err = grub_errno; > + goto finish; > + } > + > + if (grub_file_seek (f, section->raw_data_offset) == (grub_off_t) > -1 || > + grub_file_read (f, val, section->raw_data_size) != > (grub_ssize_t) section->raw_data_size) > + { > + err = grub_error (GRUB_ERR_FILE_READ_ERROR, "failed to read > section"); > + goto finish; > + } > + > + err = blsuki_add_keyval (entry, key, val); > + if (err != GRUB_ERR_NONE) > + goto finish; > + > + break; > + } > + } > + > + section_offset += sizeof (*section); > + grub_free (section); > + grub_free (val); > + grub_free (key); > + section = NULL; > + val = NULL; > + key = NULL; > + } > + > + if (has_linux == false) > + err = grub_error (GRUB_ERR_NO_KERNEL, "UKI is missing the '.linux' > section"); > + > + finish: > + grub_free (dos); > + grub_free (pe); > + grub_free (section); > + grub_free (val); > + grub_free (key); > + return err; > +} > +#endif > + > +/* > + * This function obtains the keyval pairs when the .osrel data is input into > + * the content parameter and returns the full line that it obtained the > keyval > + * pair from. > + */ > +static char * > +uki_read_osrel (char *content, grub_off_t *pos, char **key_ret, char > **val_ret) > +{ > + char *line; > + char *value; > + grub_size_t linelen; > + > + for (;;) > + { > + line = content + *pos; > + if (*line == '\0') > + return NULL; > + > + linelen = 0; > + while (line[linelen] != '\0' && line[linelen] != '\n' && line[linelen] > != '\r') > + linelen++; > + > + /* Move pos to the next line */ > + *pos += linelen; > + if (content[*pos] != '\0') > + (*pos)++; > + > + /* Skip empty line */ > + if (linelen == 0) > + continue; > + > + line[linelen] = '\0'; > + > + /* Remove leading white space */ > + while (linelen > 0 && (*line == ' ' || *line == '\t')) > + { > + line++; > + linelen--; > + } > + > + /* Remove trailing whitespace */ > + while (linelen > 0 && (line[linelen - 1] == ' ' || line[linelen - 1] > == '\t')) > + linelen--; > + line[linelen] = '\0'; > + > + if (*line == '#') > + continue; > + > + /* Split key/value */ grub_strtok() here and there? > + value = line; > + while (*value != '\0' && *value != '=') > + value++; > + if (*value == '\0') > + continue; > + *value = '\0'; > + value++; > + while (*value != '\0' && *value == '=') > + value++; > + > + /* Remove quotes from value */ > + if (*value == '\"' && line[linelen - 1] == '\"') > + { > + value++; > + line[linelen - 1] = '\0'; > + } > + > + *key_ret = line; > + *val_ret = value; > + break; > + } > + > + return line; > +} > + > struct read_entry_info > { > const char *devid; > const char *dirname; > + int cmd_type; It should be an enum not an int. And if you put this together at the beginning of the file you would realize that earlier... > grub_file_t file; > }; Daniel _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel