ac3a84
From 9489991adc3313efff58837010e53db80aebdd1b Mon Sep 17 00:00:00 2001
ac3a84
From: Jan Janssen <medhefgo@web.de>
ac3a84
Date: Tue, 22 Nov 2022 17:42:38 +0100
ac3a84
Subject: [PATCH]  stub: Fix cmdline handling
ac3a84
ac3a84
This fixes some bugs that could lead to garbage getting appended to the
ac3a84
command line passed to the kernel:
ac3a84
 1. The .cmdline section is not guaranteed to be NUL-terminated, but it
ac3a84
    was used as if it was.
ac3a84
 2. The conversion of the command line to ASCII that was passed to the
ac3a84
    stub ate the NUL at the end.
ac3a84
 3. LoadOptions is not guaranteed to be a NUL-terminated EFI string (it
ac3a84
    really should be and generally always is, though).
ac3a84
ac3a84
This also fixes the inconsistent mangling of the command line. If the
ac3a84
.cmdline section was used ASCII controls chars (new lines in particular)
ac3a84
would not be converted to spaces.
ac3a84
ac3a84
As part of this commit, we optimize conversion for the generic code
ac3a84
instead of the (deprecated) EFI handover protocol. Previously we would
ac3a84
convert to ASCII/UTF-8 and then back to EFI string for the (now) default
ac3a84
generic code path. Instead we now convert to EFI string and mangle that
ac3a84
back to ASCII in the EFI handover protocol path.
ac3a84
ac3a84
(cherry picked from commit 927ebebe588970fa2dd082a0daaef246229f009b)
ac3a84
ac3a84
Related: #2138081
ac3a84
---
ac3a84
 src/boot/efi/boot.c      | 10 ++++------
ac3a84
 src/boot/efi/linux.c     | 12 ++++++------
ac3a84
 src/boot/efi/linux.h     | 17 +++++++++++------
ac3a84
 src/boot/efi/linux_x86.c | 21 ++++++++++++++-------
ac3a84
 src/boot/efi/stub.c      | 38 +++++++++++++++++---------------------
ac3a84
 src/boot/efi/util.c      |  7 +++++++
ac3a84
 src/boot/efi/util.h      |  1 +
ac3a84
 7 files changed, 60 insertions(+), 46 deletions(-)
ac3a84
ac3a84
diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c
ac3a84
index 581043df01..426bdc3cc2 100644
ac3a84
--- a/src/boot/efi/boot.c
ac3a84
+++ b/src/boot/efi/boot.c
ac3a84
@@ -2242,13 +2242,11 @@ static void config_entry_add_unified(
ac3a84
                 content = mfree(content);
ac3a84
 
ac3a84
                 /* read the embedded cmdline file */
ac3a84
-                err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, NULL);
ac3a84
+                size_t cmdline_len;
ac3a84
+                err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, &cmdline_len);
ac3a84
                 if (err == EFI_SUCCESS) {
ac3a84
-                        /* chomp the newline */
ac3a84
-                        if (content[szs[SECTION_CMDLINE] - 1] == '\n')
ac3a84
-                                content[szs[SECTION_CMDLINE] - 1] = '\0';
ac3a84
-
ac3a84
-                        entry->options = xstr8_to_16(content);
ac3a84
+                        entry->options = xstrn8_to_16(content, cmdline_len);
ac3a84
+                        mangle_stub_cmdline(entry->options);
ac3a84
                 }
ac3a84
         }
ac3a84
 }
ac3a84
diff --git a/src/boot/efi/linux.c b/src/boot/efi/linux.c
ac3a84
index 668510fca3..48801f9dd8 100644
ac3a84
--- a/src/boot/efi/linux.c
ac3a84
+++ b/src/boot/efi/linux.c
ac3a84
@@ -93,15 +93,16 @@ static EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len,
ac3a84
 
ac3a84
 EFI_STATUS linux_exec(
ac3a84
                 EFI_HANDLE parent,
ac3a84
-                const char *cmdline, UINTN cmdline_len,
ac3a84
-                const void *linux_buffer, UINTN linux_length,
ac3a84
-                const void *initrd_buffer, UINTN initrd_length) {
ac3a84
+                const char16_t *cmdline,
ac3a84
+                const void *linux_buffer,
ac3a84
+                size_t linux_length,
ac3a84
+                const void *initrd_buffer,
ac3a84
+                size_t initrd_length) {
ac3a84
 
ac3a84
         uint32_t compat_address;
ac3a84
         EFI_STATUS err;
ac3a84
 
ac3a84
         assert(parent);
ac3a84
-        assert(cmdline || cmdline_len == 0);
ac3a84
         assert(linux_buffer && linux_length > 0);
ac3a84
         assert(initrd_buffer || initrd_length == 0);
ac3a84
 
ac3a84
@@ -113,7 +114,6 @@ EFI_STATUS linux_exec(
ac3a84
                 return linux_exec_efi_handover(
ac3a84
                                 parent,
ac3a84
                                 cmdline,
ac3a84
-                                cmdline_len,
ac3a84
                                 linux_buffer,
ac3a84
                                 linux_length,
ac3a84
                                 initrd_buffer,
ac3a84
@@ -133,7 +133,7 @@ EFI_STATUS linux_exec(
ac3a84
                 return log_error_status_stall(err, u"Error getting kernel loaded image protocol: %r", err);
ac3a84
 
ac3a84
         if (cmdline) {
ac3a84
-                loaded_image->LoadOptions = xstrn8_to_16(cmdline, cmdline_len);
ac3a84
+                loaded_image->LoadOptions = (void *) cmdline;
ac3a84
                 loaded_image->LoadOptionsSize = strsize16(loaded_image->LoadOptions);
ac3a84
         }
ac3a84
 
ac3a84
diff --git a/src/boot/efi/linux.h b/src/boot/efi/linux.h
ac3a84
index 19e5f5c4a8..f0a6a37ed1 100644
ac3a84
--- a/src/boot/efi/linux.h
ac3a84
+++ b/src/boot/efi/linux.h
ac3a84
@@ -2,14 +2,19 @@
ac3a84
 #pragma once
ac3a84
 
ac3a84
 #include <efi.h>
ac3a84
+#include <uchar.h>
ac3a84
 
ac3a84
 EFI_STATUS linux_exec(
ac3a84
                 EFI_HANDLE parent,
ac3a84
-                const char *cmdline, UINTN cmdline_len,
ac3a84
-                const void *linux_buffer, UINTN linux_length,
ac3a84
-                const void *initrd_buffer, UINTN initrd_length);
ac3a84
+                const char16_t *cmdline,
ac3a84
+                const void *linux_buffer,
ac3a84
+                size_t linux_length,
ac3a84
+                const void *initrd_buffer,
ac3a84
+                size_t initrd_length);
ac3a84
 EFI_STATUS linux_exec_efi_handover(
ac3a84
                 EFI_HANDLE parent,
ac3a84
-                const char *cmdline, UINTN cmdline_len,
ac3a84
-                const void *linux_buffer, UINTN linux_length,
ac3a84
-                const void *initrd_buffer, UINTN initrd_length);
ac3a84
+                const char16_t *cmdline,
ac3a84
+                const void *linux_buffer,
ac3a84
+                size_t linux_length,
ac3a84
+                const void *initrd_buffer,
ac3a84
+                size_t initrd_length);
ac3a84
diff --git a/src/boot/efi/linux_x86.c b/src/boot/efi/linux_x86.c
ac3a84
index 64336ce348..6a5e431107 100644
ac3a84
--- a/src/boot/efi/linux_x86.c
ac3a84
+++ b/src/boot/efi/linux_x86.c
ac3a84
@@ -126,12 +126,13 @@ static void linux_efi_handover(EFI_HANDLE parent, uintptr_t kernel, BootParams *
ac3a84
 
ac3a84
 EFI_STATUS linux_exec_efi_handover(
ac3a84
                 EFI_HANDLE parent,
ac3a84
-                const char *cmdline, UINTN cmdline_len,
ac3a84
-                const void *linux_buffer, UINTN linux_length,
ac3a84
-                const void *initrd_buffer, UINTN initrd_length) {
ac3a84
+                const char16_t *cmdline,
ac3a84
+                const void *linux_buffer,
ac3a84
+                size_t linux_length,
ac3a84
+                const void *initrd_buffer,
ac3a84
+                size_t initrd_length) {
ac3a84
 
ac3a84
         assert(parent);
ac3a84
-        assert(cmdline || cmdline_len == 0);
ac3a84
         assert(linux_buffer);
ac3a84
         assert(initrd_buffer || initrd_length == 0);
ac3a84
 
ac3a84
@@ -185,14 +186,20 @@ EFI_STATUS linux_exec_efi_handover(
ac3a84
 
ac3a84
         _cleanup_pages_ Pages cmdline_pages = {};
ac3a84
         if (cmdline) {
ac3a84
+                size_t len = MIN(strlen16(cmdline), image_params->hdr.cmdline_size);
ac3a84
+
ac3a84
                 cmdline_pages = xmalloc_pages(
ac3a84
                                 can_4g ? AllocateAnyPages : AllocateMaxAddress,
ac3a84
                                 EfiLoaderData,
ac3a84
-                                EFI_SIZE_TO_PAGES(cmdline_len + 1),
ac3a84
+                                EFI_SIZE_TO_PAGES(len + 1),
ac3a84
                                 CMDLINE_PTR_MAX);
ac3a84
 
ac3a84
-                memcpy(PHYSICAL_ADDRESS_TO_POINTER(cmdline_pages.addr), cmdline, cmdline_len);
ac3a84
-                ((char *) PHYSICAL_ADDRESS_TO_POINTER(cmdline_pages.addr))[cmdline_len] = 0;
ac3a84
+                /* Convert cmdline to ASCII. */
ac3a84
+                char *cmdline8 = PHYSICAL_ADDRESS_TO_POINTER(cmdline_pages.addr);
ac3a84
+                for (size_t i = 0; i < len; i++)
ac3a84
+                        cmdline8[i] = cmdline[i] <= 0x7E ? cmdline[i] : ' ';
ac3a84
+                cmdline8[len] = '\0';
ac3a84
+
ac3a84
                 boot_params->hdr.cmd_line_ptr = (uint32_t) cmdline_pages.addr;
ac3a84
                 boot_params->ext_cmd_line_ptr = cmdline_pages.addr >> 32;
ac3a84
                 assert(can_4g || cmdline_pages.addr <= CMDLINE_PTR_MAX);
ac3a84
diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c
ac3a84
index a842c5c679..841a0e41bd 100644
ac3a84
--- a/src/boot/efi/stub.c
ac3a84
+++ b/src/boot/efi/stub.c
ac3a84
@@ -132,14 +132,13 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) {
ac3a84
 
ac3a84
 EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
ac3a84
         _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL;
ac3a84
-        UINTN credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
ac3a84
-        UINTN cmdline_len = 0, linux_size, initrd_size, dt_size;
ac3a84
+        size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0;
ac3a84
+        size_t linux_size, initrd_size, dt_size;
ac3a84
         EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base;
ac3a84
         _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {};
ac3a84
         EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
ac3a84
-        UINTN addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
ac3a84
-        char *cmdline = NULL;
ac3a84
-        _cleanup_free_ char *cmdline_owned = NULL;
ac3a84
+        size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {};
ac3a84
+        _cleanup_free_ char16_t *cmdline = NULL;
ac3a84
         int sections_measured = -1, parameters_measured = -1;
ac3a84
         bool sysext_measured = false, m;
ac3a84
         EFI_STATUS err;
ac3a84
@@ -208,32 +207,29 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
ac3a84
         /* Show splash screen as early as possible */
ac3a84
         graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]);
ac3a84
 
ac3a84
-        if (szs[UNIFIED_SECTION_CMDLINE] > 0) {
ac3a84
-                cmdline = (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE];
ac3a84
-                cmdline_len = szs[UNIFIED_SECTION_CMDLINE];
ac3a84
-        }
ac3a84
-
ac3a84
         /* if we are not in secure boot mode, or none was provided, accept a custom command line and replace
ac3a84
          * the built-in one. We also do a superficial check whether first character of passed command line
ac3a84
          * is printable character (for compat with some Dell systems which fill in garbage?). */
ac3a84
-        if ((!secure_boot_enabled() || cmdline_len == 0) &&
ac3a84
-            loaded_image->LoadOptionsSize > 0 &&
ac3a84
+        if ((!secure_boot_enabled() || szs[UNIFIED_SECTION_CMDLINE] == 0) &&
ac3a84
+            loaded_image->LoadOptionsSize > sizeof(char16_t) &&
ac3a84
             ((char16_t *) loaded_image->LoadOptions)[0] > 0x1F) {
ac3a84
-                cmdline_len = (loaded_image->LoadOptionsSize / sizeof(char16_t)) * sizeof(char);
ac3a84
-                cmdline = cmdline_owned = xnew(char, cmdline_len);
ac3a84
-
ac3a84
-                for (UINTN i = 0; i < cmdline_len; i++) {
ac3a84
-                        char16_t c = ((char16_t *) loaded_image->LoadOptions)[i];
ac3a84
-                        cmdline[i] = c > 0x1F && c < 0x7F ? c : ' '; /* convert non-printable and non_ASCII characters to spaces. */
ac3a84
-                }
ac3a84
+                /* Note that LoadOptions is a void*, so it could be anything! */
ac3a84
+                cmdline = xstrndup16(
ac3a84
+                                loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t));
ac3a84
+                mangle_stub_cmdline(cmdline);
ac3a84
 
ac3a84
                 /* Let's measure the passed kernel command line into the TPM. Note that this possibly
ac3a84
                  * duplicates what we already did in the boot menu, if that was already used. However, since
ac3a84
                  * we want the boot menu to support an EFI binary, and want to this stub to be usable from
ac3a84
                  * any boot menu, let's measure things anyway. */
ac3a84
                 m = false;
ac3a84
-                (void) tpm_log_load_options(loaded_image->LoadOptions, &m);
ac3a84
+                (void) tpm_log_load_options(cmdline, &m);
ac3a84
                 parameters_measured = m;
ac3a84
+        } else if (szs[UNIFIED_SECTION_CMDLINE] > 0) {
ac3a84
+                cmdline = xstrn8_to_16(
ac3a84
+                                (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE],
ac3a84
+                                szs[UNIFIED_SECTION_CMDLINE]);
ac3a84
+                mangle_stub_cmdline(cmdline);
ac3a84
         }
ac3a84
 
ac3a84
         export_variables(loaded_image);
ac3a84
@@ -374,7 +370,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
ac3a84
                         log_error_stall(L"Error loading embedded devicetree: %r", err);
ac3a84
         }
ac3a84
 
ac3a84
-        err = linux_exec(image, cmdline, cmdline_len,
ac3a84
+        err = linux_exec(image, cmdline,
ac3a84
                          PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size,
ac3a84
                          PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size);
ac3a84
         graphics_mode(false);
ac3a84
diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c
ac3a84
index 3268c511d0..1f07fbc38c 100644
ac3a84
--- a/src/boot/efi/util.c
ac3a84
+++ b/src/boot/efi/util.c
ac3a84
@@ -274,6 +274,13 @@ char16_t *xstr8_to_path(const char *str8) {
ac3a84
         return path;
ac3a84
 }
ac3a84
 
ac3a84
+void mangle_stub_cmdline(char16_t *cmdline) {
ac3a84
+        for (; *cmdline != '\0'; cmdline++)
ac3a84
+                /* Convert ASCII control characters to spaces. */
ac3a84
+                if (*cmdline <= 0x1F)
ac3a84
+                        *cmdline = ' ';
ac3a84
+}
ac3a84
+
ac3a84
 EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, UINTN off, UINTN size, char **ret, UINTN *ret_size) {
ac3a84
         _cleanup_(file_closep) EFI_FILE *handle = NULL;
ac3a84
         _cleanup_free_ char *buf = NULL;
ac3a84
diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h
ac3a84
index e4ab8138c4..f58d24fce1 100644
ac3a84
--- a/src/boot/efi/util.h
ac3a84
+++ b/src/boot/efi/util.h
ac3a84
@@ -114,6 +114,7 @@ EFI_STATUS efivar_get_boolean_u8(const EFI_GUID *vendor, const char16_t *name, b
ac3a84
 
ac3a84
 void convert_efi_path(char16_t *path);
ac3a84
 char16_t *xstr8_to_path(const char *stra);
ac3a84
+void mangle_stub_cmdline(char16_t *cmdline);
ac3a84
 
ac3a84
 EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, UINTN off, UINTN size, char **content, UINTN *content_size);
ac3a84