diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml
index c230168780..7934f344f8 100644
--- a/man/systemd-stub.xml
+++ b/man/systemd-stub.xml
@@ -379,6 +379,23 @@
default, this is done for the TPM2 PCR signature and public key files.
+
+ SMBIOS Type 11 Strings
+
+ systemd-stub can be configured using SMBIOS Type 11 strings. Applicable strings
+ consist of a name, followed by =, followed by the value.
+ systemd-stub will search the table for a string with a specific name, and if found,
+ use its value. The following strings are read:
+
+
+
+ io.systemd.stub.kernel-cmdline-extra
+ If set, the value of this string is added to the list of kernel command line
+ arguments that are passed to the kernel.
+
+
+
+
Assembling Kernel Images
diff --git a/src/boot/efi/efi-string.c b/src/boot/efi/efi-string.c
index ee3dc1c4a9..a94e2e4c17 100644
--- a/src/boot/efi/efi-string.c
+++ b/src/boot/efi/efi-string.c
@@ -112,7 +112,7 @@ DEFINE_STRCPY(char16_t, strcpy16);
s++; \
} \
\
- return NULL; \
+ return c ? NULL : (type *) s; \
}
DEFINE_STRCHR(char, strchr8);
@@ -212,6 +212,21 @@ char16_t *xstrn8_to_16(const char *str8, size_t n) {
return str16;
}
+char *startswith8(const char *s, const char *prefix) {
+ size_t l;
+
+ assert(prefix);
+
+ if (!s)
+ return NULL;
+
+ l = strlen8(prefix);
+ if (!strneq8(s, prefix, l))
+ return NULL;
+
+ return (char*) s + l;
+}
+
static bool efi_fnmatch_prefix(const char16_t *p, const char16_t *h, const char16_t **ret_p, const char16_t **ret_h) {
assert(p);
assert(h);
@@ -863,16 +878,30 @@ char16_t *xvasprintf_status(EFI_STATUS status, const char *format, va_list ap) {
#if SD_BOOT
/* To provide the actual implementation for these we need to remove the redirection to the builtins. */
+# undef memchr
# undef memcmp
# undef memcpy
# undef memset
#else
/* And for userspace unit testing we need to give them an efi_ prefix. */
+# define memchr efi_memchr
# define memcmp efi_memcmp
# define memcpy efi_memcpy
# define memset efi_memset
#endif
+_used_ void *memchr(const void *p, int c, size_t n) {
+ if (!p || n == 0)
+ return NULL;
+
+ const uint8_t *q = p;
+ for (size_t i = 0; i < n; i++)
+ if (q[i] == (unsigned char) c)
+ return (void *) (q + i);
+
+ return NULL;
+}
+
_used_ int memcmp(const void *p1, const void *p2, size_t n) {
const uint8_t *up1 = p1, *up2 = p2;
int r;
diff --git a/src/boot/efi/efi-string.h b/src/boot/efi/efi-string.h
index 6b6b0d57b4..410bfd8ef5 100644
--- a/src/boot/efi/efi-string.h
+++ b/src/boot/efi/efi-string.h
@@ -101,6 +101,8 @@ static inline char16_t *xstr8_to_16(const char *str8) {
return xstrn8_to_16(str8, strlen8(str8));
}
+char *startswith8(const char *s, const char *prefix);
+
bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack);
bool parse_number8(const char *s, uint64_t *ret_u, const char **ret_tail);
@@ -151,6 +153,7 @@ _gnu_printf_(2, 0) _warn_unused_result_ char16_t *xvasprintf_status(EFI_STATUS s
* compiling with -ffreestanding. By referring to builtins, the compiler can check arguments and do
* optimizations again. Note that we still need to provide implementations as the compiler is free to not
* inline its own implementation and instead issue a library call. */
+# define memchr __builtin_memchr
# define memcmp __builtin_memcmp
# define memcpy __builtin_memcpy
# define memset __builtin_memset
@@ -164,6 +167,7 @@ static inline void *mempcpy(void * restrict dest, const void * restrict src, siz
#else
/* For unit testing. */
+void *efi_memchr(const void *p, int c, size_t n);
int efi_memcmp(const void *p1, const void *p2, size_t n);
void *efi_memcpy(void * restrict dest, const void * restrict src, size_t n);
void *efi_memset(void *p, int c, size_t n);
diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c
index 2635445b04..25c81ca164 100644
--- a/src/boot/efi/stub.c
+++ b/src/boot/efi/stub.c
@@ -14,6 +14,7 @@
#include "splash.h"
#include "tpm-pcr.h"
#include "util.h"
+#include "vmm.h"
/* magic string to find in the binary image */
_used_ _section_(".sdmagic") static const char magic[] = "#### LoaderInfo: systemd-stub " GIT_VERSION " ####";
@@ -275,6 +276,12 @@ static EFI_STATUS run(EFI_HANDLE image) {
mangle_stub_cmdline(cmdline);
}
+ const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra");
+ if (extra) {
+ _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), *extra16 = xstr8_to_16(extra);
+ cmdline = xasprintf("%ls %ls", tmp, extra16);
+ }
+
export_variables(loaded_image);
if (pack_cpio(loaded_image,
diff --git a/src/boot/efi/test-efi-string.c b/src/boot/efi/test-efi-string.c
index c26973d8bd..d214b1536e 100644
--- a/src/boot/efi/test-efi-string.c
+++ b/src/boot/efi/test-efi-string.c
@@ -229,6 +229,8 @@ TEST(strchr8) {
assert_se(strchr8(str, 'a') == &str[0]);
assert_se(strchr8(str, 'c') == &str[2]);
assert_se(strchr8(str, 'B') == &str[4]);
+
+ assert_se(strchr8(str, 0) == str + strlen8(str));
}
TEST(strchr16) {
@@ -240,6 +242,8 @@ TEST(strchr16) {
assert_se(strchr16(str, 'a') == &str[0]);
assert_se(strchr16(str, 'c') == &str[2]);
assert_se(strchr16(str, 'B') == &str[4]);
+
+ assert_se(strchr16(str, 0) == str + strlen16(str));
}
TEST(xstrndup8) {
@@ -351,6 +355,18 @@ TEST(xstrn8_to_16) {
free(s);
}
+TEST(startswith8) {
+ assert_se(streq8(startswith8("", ""), ""));
+ assert_se(streq8(startswith8("x", ""), "x"));
+ assert_se(!startswith8("", "x"));
+ assert_se(!startswith8("", "xxxxxxxx"));
+ assert_se(streq8(startswith8("xxx", "x"), "xx"));
+ assert_se(streq8(startswith8("xxx", "xx"), "x"));
+ assert_se(streq8(startswith8("xxx", "xxx"), ""));
+ assert_se(!startswith8("xxx", "xxxx"));
+ assert_se(!startswith8(NULL, ""));
+}
+
#define TEST_FNMATCH_ONE(pattern, haystack, expect) \
({ \
assert_se(fnmatch(pattern, haystack, 0) == (expect ? 0 : FNM_NOMATCH)); \
@@ -610,6 +626,19 @@ TEST(xvasprintf_status) {
s = mfree(s);
}
+TEST(efi_memchr) {
+ assert_se(streq8(efi_memchr("abcde", 'c', 5), "cde"));
+ assert_se(streq8(efi_memchr("abcde", 'c', 3), "cde"));
+ assert_se(streq8(efi_memchr("abcde", 'c', 2), NULL));
+ assert_se(streq8(efi_memchr("abcde", 'c', 7), "cde"));
+ assert_se(streq8(efi_memchr("abcde", 'q', 5), NULL));
+ assert_se(streq8(efi_memchr("abcde", 'q', 0), NULL));
+ /* Test that the character is interpreted as unsigned char. */
+ assert_se(streq8(efi_memchr("abcde", 'a', 6), efi_memchr("abcde", 'a' + 0x100, 6)));
+ assert_se(streq8(efi_memchr("abcde", 0, 6), ""));
+ assert_se(efi_memchr(NULL, 0, 0) == NULL);
+}
+
TEST(efi_memcmp) {
assert_se(efi_memcmp(NULL, NULL, 0) == 0);
assert_se(efi_memcmp(NULL, NULL, 1) == 0);
diff --git a/src/boot/efi/vmm.c b/src/boot/efi/vmm.c
index f5c17a9eca..c0e8823fe6 100644
--- a/src/boot/efi/vmm.c
+++ b/src/boot/efi/vmm.c
@@ -184,17 +184,23 @@ typedef struct {
uint8_t bios_characteristics_ext[2];
} _packed_ SmbiosTableType0;
-static void *find_smbios_configuration_table(uint64_t *ret_size) {
+typedef struct {
+ SmbiosHeader header;
+ uint8_t count;
+ char contents[];
+} _packed_ SmbiosTableType11;
+
+static const void *find_smbios_configuration_table(uint64_t *ret_size) {
assert(ret_size);
- Smbios3EntryPoint *entry3 = find_configuration_table(MAKE_GUID_PTR(SMBIOS3_TABLE));
+ const Smbios3EntryPoint *entry3 = find_configuration_table(MAKE_GUID_PTR(SMBIOS3_TABLE));
if (entry3 && memcmp(entry3->anchor_string, "_SM3_", 5) == 0 &&
entry3->entry_point_length <= sizeof(*entry3)) {
*ret_size = entry3->table_maximum_size;
return PHYSICAL_ADDRESS_TO_POINTER(entry3->table_address);
}
- SmbiosEntryPoint *entry = find_configuration_table(MAKE_GUID_PTR(SMBIOS_TABLE));
+ const SmbiosEntryPoint *entry = find_configuration_table(MAKE_GUID_PTR(SMBIOS_TABLE));
if (entry && memcmp(entry->anchor_string, "_SM_", 4) == 0 &&
entry->entry_point_length <= sizeof(*entry)) {
*ret_size = entry->table_length;
@@ -204,9 +210,9 @@ static void *find_smbios_configuration_table(uint64_t *ret_size) {
return NULL;
}
-static SmbiosHeader *get_smbios_table(uint8_t type) {
+static const SmbiosHeader *get_smbios_table(uint8_t type, uint64_t *ret_size_left) {
uint64_t size = 0;
- uint8_t *p = find_smbios_configuration_table(&size);
+ const uint8_t *p = find_smbios_configuration_table(&size);
if (!p)
return false;
@@ -214,7 +220,7 @@ static SmbiosHeader *get_smbios_table(uint8_t type) {
if (size < sizeof(SmbiosHeader))
return NULL;
- SmbiosHeader *header = (SmbiosHeader *) p;
+ const SmbiosHeader *header = (const SmbiosHeader *) p;
/* End of table. */
if (header->type == 127)
@@ -223,8 +229,11 @@ static SmbiosHeader *get_smbios_table(uint8_t type) {
if (size < header->length)
return NULL;
- if (header->type == type)
+ if (header->type == type) {
+ if (ret_size_left)
+ *ret_size_left = size;
return header; /* Yay! */
+ }
/* Skip over formatted area. */
size -= header->length;
@@ -232,22 +241,18 @@ static SmbiosHeader *get_smbios_table(uint8_t type) {
/* Skip over string table. */
for (;;) {
- while (size > 0 && *p != '\0') {
+ const uint8_t *e = memchr(p, 0, size);
+ if (!e)
+ return NULL;
+
+ if (e == p) {/* Double NUL byte means we've reached the end of the string table. */
p++;
size--;
- }
- if (size == 0)
- return NULL;
- p++;
- size--;
-
- /* Double NUL terminates string table. */
- if (*p == '\0') {
- if (size == 0)
- return NULL;
- p++;
break;
}
+
+ size -= e + 1 - p;
+ p = e + 1;
}
}
@@ -256,7 +261,7 @@ static SmbiosHeader *get_smbios_table(uint8_t type) {
static bool smbios_in_hypervisor(void) {
/* Look up BIOS Information (Type 0). */
- SmbiosTableType0 *type0 = (SmbiosTableType0 *) get_smbios_table(0);
+ const SmbiosTableType0 *type0 = (const SmbiosTableType0 *) get_smbios_table(0, NULL);
if (!type0 || type0->header.length < sizeof(SmbiosTableType0))
return false;
@@ -272,3 +277,32 @@ bool in_hypervisor(void) {
cache = cpuid_in_hypervisor() || smbios_in_hypervisor();
return cache;
}
+
+const char* smbios_find_oem_string(const char *name) {
+ uint64_t left;
+
+ assert(name);
+
+ const SmbiosTableType11 *type11 = (const SmbiosTableType11 *) get_smbios_table(11, &left);
+ if (!type11 || type11->header.length < sizeof(SmbiosTableType11))
+ return NULL;
+
+ assert(left >= type11->header.length);
+
+ const char *s = type11->contents;
+ left -= type11->header.length;
+
+ for (const char *p = s; p < s + left; ) {
+ const char *e = memchr(p, 0, s + left - p);
+ if (!e || e == p) /* Double NUL byte means we've reached the end of the OEM strings. */
+ break;
+
+ const char *eq = startswith8(p, name);
+ if (eq && *eq == '=')
+ return eq + 1;
+
+ p = e + 1;
+ }
+
+ return NULL;
+}
diff --git a/src/boot/efi/vmm.h b/src/boot/efi/vmm.h
index 32f4fa3345..061ad692ec 100644
--- a/src/boot/efi/vmm.h
+++ b/src/boot/efi/vmm.h
@@ -7,3 +7,5 @@ bool is_direct_boot(EFI_HANDLE device);
EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir);
bool in_hypervisor(void);
+
+const char* smbios_find_oem_string(const char *name);