Skip to content

Commit 4a568ce

Browse files
nivedita76ardbiesheuvel
authored andcommitted
efi/x86: Add a quirk to support command line arguments on Dell EFI firmware
At least some versions of Dell EFI firmware pass the entire EFI_LOAD_OPTION descriptor, rather than just the OptionalData part, to the loaded image. This was verified with firmware revision 2.15.0 on a Dell Precision T3620 by Jacobo Pantoja. To handle this, add a quirk to check if the options look like a valid EFI_LOAD_OPTION descriptor, and if so, use the OptionalData part as the command line. Signed-off-by: Arvind Sankar <nivedita@alum.mit.edu> Reported-by: Jacobo Pantoja <jacobopantoja@gmail.com> Link: https://lore.kernel.org/linux-efi/20200907170021.GA2284449@rani.riverdale.lan/ Link: https://lore.kernel.org/r/20200914213535.933454-2-nivedita@alum.mit.edu Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
1 parent c1df5e0 commit 4a568ce

3 files changed

Lines changed: 135 additions & 2 deletions

File tree

drivers/firmware/efi/libstub/efi-stub-helper.c

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,102 @@ efi_status_t efi_parse_options(char const *cmdline)
230230
return EFI_SUCCESS;
231231
}
232232

233+
/*
234+
* The EFI_LOAD_OPTION descriptor has the following layout:
235+
* u32 Attributes;
236+
* u16 FilePathListLength;
237+
* u16 Description[];
238+
* efi_device_path_protocol_t FilePathList[];
239+
* u8 OptionalData[];
240+
*
241+
* This function validates and unpacks the variable-size data fields.
242+
*/
243+
static
244+
bool efi_load_option_unpack(efi_load_option_unpacked_t *dest,
245+
const efi_load_option_t *src, size_t size)
246+
{
247+
const void *pos;
248+
u16 c;
249+
efi_device_path_protocol_t header;
250+
const efi_char16_t *description;
251+
const efi_device_path_protocol_t *file_path_list;
252+
253+
if (size < offsetof(efi_load_option_t, variable_data))
254+
return false;
255+
pos = src->variable_data;
256+
size -= offsetof(efi_load_option_t, variable_data);
257+
258+
if ((src->attributes & ~EFI_LOAD_OPTION_MASK) != 0)
259+
return false;
260+
261+
/* Scan description. */
262+
description = pos;
263+
do {
264+
if (size < sizeof(c))
265+
return false;
266+
c = *(const u16 *)pos;
267+
pos += sizeof(c);
268+
size -= sizeof(c);
269+
} while (c != L'\0');
270+
271+
/* Scan file_path_list. */
272+
file_path_list = pos;
273+
do {
274+
if (size < sizeof(header))
275+
return false;
276+
header = *(const efi_device_path_protocol_t *)pos;
277+
if (header.length < sizeof(header))
278+
return false;
279+
if (size < header.length)
280+
return false;
281+
pos += header.length;
282+
size -= header.length;
283+
} while ((header.type != EFI_DEV_END_PATH && header.type != EFI_DEV_END_PATH2) ||
284+
(header.sub_type != EFI_DEV_END_ENTIRE));
285+
if (pos != (const void *)file_path_list + src->file_path_list_length)
286+
return false;
287+
288+
dest->attributes = src->attributes;
289+
dest->file_path_list_length = src->file_path_list_length;
290+
dest->description = description;
291+
dest->file_path_list = file_path_list;
292+
dest->optional_data_size = size;
293+
dest->optional_data = size ? pos : NULL;
294+
295+
return true;
296+
}
297+
298+
/*
299+
* At least some versions of Dell firmware pass the entire contents of the
300+
* Boot#### variable, i.e. the EFI_LOAD_OPTION descriptor, rather than just the
301+
* OptionalData field.
302+
*
303+
* Detect this case and extract OptionalData.
304+
*/
305+
void efi_apply_loadoptions_quirk(const void **load_options, int *load_options_size)
306+
{
307+
const efi_load_option_t *load_option = *load_options;
308+
efi_load_option_unpacked_t load_option_unpacked;
309+
310+
if (!IS_ENABLED(CONFIG_X86))
311+
return;
312+
if (!load_option)
313+
return;
314+
if (*load_options_size < sizeof(*load_option))
315+
return;
316+
if ((load_option->attributes & ~EFI_LOAD_OPTION_BOOT_MASK) != 0)
317+
return;
318+
319+
if (!efi_load_option_unpack(&load_option_unpacked, load_option, *load_options_size))
320+
return;
321+
322+
efi_warn_once(FW_BUG "LoadOptions is an EFI_LOAD_OPTION descriptor\n");
323+
efi_warn_once(FW_BUG "Using OptionalData as a workaround\n");
324+
325+
*load_options = load_option_unpacked.optional_data;
326+
*load_options_size = load_option_unpacked.optional_data_size;
327+
}
328+
233329
/*
234330
* Convert the unicode UEFI command line to ASCII to pass to kernel.
235331
* Size of memory allocated return in *cmd_line_len.
@@ -239,12 +335,15 @@ char *efi_convert_cmdline(efi_loaded_image_t *image, int *cmd_line_len)
239335
{
240336
const u16 *s2;
241337
unsigned long cmdline_addr = 0;
242-
int options_chars = efi_table_attr(image, load_options_size) / 2;
338+
int options_chars = efi_table_attr(image, load_options_size);
243339
const u16 *options = efi_table_attr(image, load_options);
244340
int options_bytes = 0, safe_options_bytes = 0; /* UTF-8 bytes */
245341
bool in_quote = false;
246342
efi_status_t status;
247343

344+
efi_apply_loadoptions_quirk((const void **)&options, &options_chars);
345+
options_chars /= sizeof(*options);
346+
248347
if (options) {
249348
s2 = options;
250349
while (options_bytes < COMMAND_LINE_SIZE && options_chars--) {

drivers/firmware/efi/libstub/efistub.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,35 @@ union efi_load_file_protocol {
708708
} mixed_mode;
709709
};
710710

711+
typedef struct {
712+
u32 attributes;
713+
u16 file_path_list_length;
714+
u8 variable_data[];
715+
// efi_char16_t description[];
716+
// efi_device_path_protocol_t file_path_list[];
717+
// u8 optional_data[];
718+
} __packed efi_load_option_t;
719+
720+
#define EFI_LOAD_OPTION_ACTIVE 0x0001U
721+
#define EFI_LOAD_OPTION_FORCE_RECONNECT 0x0002U
722+
#define EFI_LOAD_OPTION_HIDDEN 0x0008U
723+
#define EFI_LOAD_OPTION_CATEGORY 0x1f00U
724+
#define EFI_LOAD_OPTION_CATEGORY_BOOT 0x0000U
725+
#define EFI_LOAD_OPTION_CATEGORY_APP 0x0100U
726+
727+
#define EFI_LOAD_OPTION_BOOT_MASK \
728+
(EFI_LOAD_OPTION_ACTIVE|EFI_LOAD_OPTION_HIDDEN|EFI_LOAD_OPTION_CATEGORY)
729+
#define EFI_LOAD_OPTION_MASK (EFI_LOAD_OPTION_FORCE_RECONNECT|EFI_LOAD_OPTION_BOOT_MASK)
730+
731+
typedef struct {
732+
u32 attributes;
733+
u16 file_path_list_length;
734+
const efi_char16_t *description;
735+
const efi_device_path_protocol_t *file_path_list;
736+
size_t optional_data_size;
737+
const void *optional_data;
738+
} efi_load_option_unpacked_t;
739+
711740
void efi_pci_disable_bridge_busmaster(void);
712741

713742
typedef efi_status_t (*efi_exit_boot_map_processing)(
@@ -750,6 +779,8 @@ __printf(1, 2) int efi_printk(char const *fmt, ...);
750779

751780
void efi_free(unsigned long size, unsigned long addr);
752781

782+
void efi_apply_loadoptions_quirk(const void **load_options, int *load_options_size);
783+
753784
char *efi_convert_cmdline(efi_loaded_image_t *image, int *cmd_line_len);
754785

755786
efi_status_t efi_get_memory_map(struct efi_boot_memmap *map);

drivers/firmware/efi/libstub/file.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
136136
unsigned long *load_size)
137137
{
138138
const efi_char16_t *cmdline = image->load_options;
139-
int cmdline_len = image->load_options_size / 2;
139+
int cmdline_len = image->load_options_size;
140140
unsigned long efi_chunk_size = ULONG_MAX;
141141
efi_file_protocol_t *volume = NULL;
142142
efi_file_protocol_t *file;
@@ -148,6 +148,9 @@ efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
148148
if (!load_addr || !load_size)
149149
return EFI_INVALID_PARAMETER;
150150

151+
efi_apply_loadoptions_quirk((const void **)&cmdline, &cmdline_len);
152+
cmdline_len /= sizeof(*cmdline);
153+
151154
if (IS_ENABLED(CONFIG_X86) && !efi_nochunk)
152155
efi_chunk_size = EFI_READ_CHUNK_SIZE;
153156

0 commit comments

Comments
 (0)