With no console= given the kernel will use the graphical console and if
we give one console= then that will be used instead. But sometimes we
want both a serial console and a graphical one to work. This would be
consistent with the EFI menu and sd-boot showing on both already. It
also makes the impact of a wrongly detected-but-missing VirtIO console
lower when we emit it alongside of the other consoles we detect.
Collect all detected consoles and emit them via console= with the same
priority we used before to select them. This means for the main console
there is no change but we get additional ones enabled. This helps with
boot output and having a login presented, yet the main console is still
special and gets the emergency output at boot (we should somehow surface
that on the additional consoles but that's another topic). If we only
see the graphical console (or none), we don't need to emit anything a
the kernel already selects it itself. This also avoids suppressing the
non-x86 kernel serial console detection. As mentioned above, the VirtIO
console being wrongly added is now also less impactful. But for non-x86
ACPI case we could detect the serial so that console=hvc0 won't stop the
kernel serial auto-detection.
With vmspawn --console=gui and similar one has the case where the VirtIO
PCI device is present for something else than the console and the logic
in sd-stub added console=hvc0 even though it didn't exist. This caused
tty0 to be empty during boot.
Always prefer the graphical console before a potential virtio console.
The virtio console is still preferred over a serial console and we can
get the same problem there, e.g., qemu-guest-agent is used causing the
PCI device but without a virtio console and instead a serial console is
used. This is not solved here.
The structure length comes from the firmware SMBIOS table and the only
bound before the fixed-offset wake_up_type write was an assert(), which
is a no-op in release sd-boot builds.
Whether the field is present is governed by the formatted-area length
header->length, not the total size which also covers the trailing string
set. A too-short record (an old SMBIOS 2.0 one, or a crafted 6-byte
header) makes the offset-24 write land past the xmemdup() copy; a record
with a short formatted area but long string set instead gets a stable
string byte zeroed in the copy. Key the guard off header->length and the
field offset, matching get_smbios_table(), and measure such records
as-is. Zero only the part of the field that lies within the formatted
area, and collapse the two measure_smbios_raw() call sites by selecting
what to measure via a pointer.
Signed-off-by: Syed Mohammed Nayyar <jmestwa@gmail.com>
verify_pe() only checked SizeOfOptionalHeader against a SIZE_MAX wrap (a
clause that, given SizeOfOptionalHeader is a uint16_t, can never reject
anything) and never read NumberOfRvaAndSizes. But pe_kernel_info(),
pe_kernel_check_nx_compat() and pe_kernel_check_no_relocation() then read
SizeOfImage, AddressOfEntryPoint, DllCharacteristics and the base
relocation data directory entry from the optional header.
Require SizeOfOptionalHeader to be large enough to contain everything down
to the base relocation data directory entry, and require the image to
declare that many data directory entries.
Follow-up for bacc2ed0d5
linux_exec() patches the stub's own EFI_LOADED_IMAGE_PROTOCOL to point
at the loaded inner kernel, and restores the saved original only after
the entry point returns. The initrd_register() failure path returns
without restoring, leaving the firmware's protocol pointing to freed
data.
Follow-up for f405165065
linux_exec() marks code sections RO+X for W^X and reverts them to RW+NX
in a loop just before returning, because EDK2 requires freed buffers to
be writable and non-executable or FreePages() crashes. Not every error
path is currently covered. Switch to a _cleanup_ helper so that every
return path is covered.
Follow-up for 56d19b633d
pe_locate_sections_internal() stores each matching section's VirtualSize
and VirtualAddress into PeSectionVector.memory_size/memory_offset with
only SIZE_MAX overflow guards, never checking them against the image's
SizeOfImage.
Wire up the image's SizeOfImage down to pe_locate_sections_internal()
and skip any section whose in-memory section does not fit within it.
Follow-up for fb974ac485
The inner-kernel section loader checks VirtualAddress + SizeOfRawData
against kernel_size_in_memory (for the memcpy), but the memzero right
after it clears up to VirtualAddress + VirtualSize, and VirtualSize is
only constrained to be >= SizeOfRawData.
Reject a VirtualAddress + VirtualSize that overflows or exceeds
kernel_size_in_memory, mirroring the existing SizeOfRawData checks.
Follow-up for cab9c7b5a4
pe_kernel_info() returned AddressOfEntryPoint (and the .compat section
entry_point) straight from the PE header with no check against
SizeOfImage. Since cab9c7b5a4 the stub calls the inner kernel directly
as ImageBase + entry_point, and only EFI_SIZE_TO_PAGES(SizeOfImage) pages
are allocated for it.
Follow-up for cab9c7b5a4
de0da85d41 switched the unquote check to strchr8(QUOTES, value[0]),
which is not equivalent to the old explicit comparison for an empty
value: strchr8(), like strchr(3), returns a pointer to the haystack's
terminating NUL when the needle is '\0', so strchr8(QUOTES, '\0') is
non-NULL. For a line whose separator is the last byte (e.g. "ID=") the
split leaves value[0] == '\0' and line[linelen - 1] == '\0' too, so both
conjuncts hold and value++ steps one byte past the value's terminator.
Follow-up for de0da85d41
When a variable exists but is empty, the initial size-query
GetVariable() in efivar_get_raw_full() returns EFI_SUCCESS instead of
EFI_BUFFER_TOO_SMALL: the zero-length payload already "fits" the
zero-length query buffer. The helper returns success, but does not
initialize the return parameters.
Handle a couple of corner cases by checking the return size.
Follow-up for a409607489
device_path_next_node() advances by the current node's Length field,
which per the EFI device path protocol includes the 4-byte node header;
a well-formed node is therefore at least sizeof(EFI_DEVICE_PATH) bytes
long. A malformed node with Length < sizeof(EFI_DEVICE_PATH), in
particular Length == 0, makes the helper return its input pointer
unchanged.
Advance by at least sizeof(EFI_DEVICE_PATH).
Follow-up for 5080a60a71
Commit 0cf5f816f2 replaced the original lower-bound check
if (h->SizeOfPartitionEntry < sizeof(EFI_PARTITION_ENTRY))
return false;
with a multiple-of check
if ((h->SizeOfPartitionEntry % sizeof(EFI_PARTITION_ENTRY)) != 0)
return false;
to additionally require the entry size to be a multiple of 128. The
modulo test is however also satisfied by SizeOfPartitionEntry == 0, so a
GPT header advertising a zero entry size now passes verify_gpt().
Restore the lower bound in addition to the multiple-of check, so the
entry size must be at least sizeof(EFI_PARTITION_ENTRY) and a multiple of
it (128 bytes).
Follow-up for 0cf5f816f2
In commit 29c6d1c125 ("boot: measure select SMBIOS objects
explicitly") we added an explicit SMBIOS measurement in systemd-boot
and systemd-stub, covering cases where firmware doesn't measure SMBIOS
itself. measure_smbios() bailed unless a TPM was present, so it skipped
the measurement on confidential guests that only expose a CC
measurement protocol (e.g. Intel TDX RTMRs).
We can have much higher expectations of the virtual firmware used for
confidential computing, and the firmware is attested, so we can expect
it to always measure SMBIOS in this case. We still do our own
measurement to get a measurement structure similar to that of
non-confidential guests, and as another line of defense.
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
When the random seed file doesn't exist but we have good entropy
(seeded_by_efi=true), we attempt to create it. This requires a handle
to the \loader\ directory, which may not exist on systems using
UKI+EFISTUB without systemd-boot installed.
Obtain the directory handle by first trying a read-only open; if that
returns EFI_NOT_FOUND, create the directory. We deliberately avoid
requesting write access on an already-present directory because some
firmware implementations return EFI_INVALID_PARAMETER for a
WRITE|CREATE open on an existing directory — this would be logged at
LOG_ERR and abort seed creation on systems where \loader\ exists but
random-seed does not (the normal systemd-boot layout).
Once a handle to \loader\ is obtained, open the seed file relative to
that handle rather than using the full path from root.
Introduced-by: c0e7046c17 ("boot: log about RO I/O errors at debug level.")
Fixes: #42801
Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
vmm.c carries the confidential-VM detection used by sd-boot/sd-stub.
Its detect_tdx() had the same dead guard as the userspace copy: it
gated the 0x21 read on CPUID_GET_HIGHEST_FUNCTION (0x80000000, the
extended max function), which is always >= 0x80000000, so the guard
never held.
Mirror the userspace fix: read leaf 0x21 directly and rely on the
IntelTDX signature, matching the kernel. An out-of-range CPUID leaf
returns the highest basic leaf's data (no fault), and 0x21 is a
synthetic TDX leaf whose presence need not be reflected in the max
basic function, so it must not be gated on it.
Ref: Linux 59bd54a84d15 ("x86/tdx: Detect running as a TDX guest in
early boot"), arch/x86/coco/tdx/tdx.c:1119 (tdx_early_init()).
Signed-off-by: Paul Meyer <katexochen0@gmail.com>
UEFI firmware type hwids must be matched against the current hardware first.
This change implements that. Additionally, some extra validations on the hwids
entries have also been added.
The stub copies the embedded kernel PE into an xmalloc_pages()
allocation with AllocateAnyPages, which only guarantees EFI_PAGE_SIZE
(4 KiB) alignment.
The arm64 kernel declares SectionAlignment = SZ_64K. When _text is not
64K-aligned the kernel's EFI stub reallocates and copies the image,
which can fail with EFI_OUT_OF_RESOURCES on memory-constrained firmware
(e.g. U-Boot, bounded by CONFIG_SYS_MALLOC_LEN rather than full DRAM).
The failure is non-deterministic since AllocateAnyPages returns whatever
page the allocator finds first.
Plumb SectionAlignment through pe_kernel_info() and allocate via
xmalloc_aligned_pages(). For alignment <= EFI_PAGE_SIZE (e.g. x86_64) it
reduces to a plain AllocatePages(), so the change is free there.
SectionAlignment comes from the PE header, which the spec requires to be
a power of 2. Sanitize it in pe_kernel_info() right where it is parsed:
a non-conforming value falls back to plain page alignment (matching the
behavior of older systemd-stub versions that ignored the field) rather
than propagate something that would break xmalloc_aligned_pages()'s
over-alignment maths, with a log_warning() so the fallback stays
diagnosable.
Co-developed-by: Claude <claude@anthropic.com>
Closes: #42443
Reported-By: Agathe Porte <agathe.porte@oss.qualcomm.com>
Tested-By: Agathe Porte <agathe.porte@oss.qualcomm.com>
This will also allow debugging systemd-boot easily on RISC-V.
Note that the following much simpler variant won't work, as we might be missing
the optional 'zihintpause' extension:
asm volatile("pause");
Signed-off-by: Marcel Ziswiler <marcel.ziswiler@codethink.co.uk>
The UEFI specification only defines A/C/I/M/Zicsr/Zifencei as mandatory
extensions in boot services. However, on systems with everything built
with F/D support, its difficult to disable F/D without changing a
toolchain. In addition, both EDK2 and U-Boot enable F/D on boot,
although they neither enable nor disable V. EDK2 comments even claim the
enablement of FPU is "to be compliant with UEFI spec" despite the spec
requires dynamic detection before using F/D.
Add corresponding -march flags to prevent systemd-boot from using other
extensions on RISC-V, and a comment for the temporary enablement of F/D.
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Inspired by #42307, but does not fix it. We just recognize more types of
errors that can be triggered by RO media, and downgrade to debug log
level.
Inspired-by: #42307
Drop the -fundamental suffix from src/fundamental/ headers in favor of names
that match their src/basic/ or src/shared/ counterparts (e.g.
macro-fundamental.h -> macro.h, assert-fundamental.h -> assert-util.h,
cleanup-fundamental.h -> cleanup-util.h). Rename src/basic/{btrfs,label}.{c,h}
to use the -util suffix to match the existing shared/btrfs-util and
shared/label-util siblings. Rename src/shared/mkdir-label.{c,h} to mkdir.{c,h}
and src/shared/tmpfile-util-label.{c,h} to tmpfile-util.{c,h} to match the
corresponding src/basic names.
This saves us from having to come up with separate names for files that do
the same thing across tiers, and it makes it easier to move stuff between
src/fundamental, src/basic and src/shared: consumers just #include "foo.h"
and pick up whichever tier their -I path resolves to first, so call sites
don't need to be updated when an API moves between layers.
Where a higher-tier wrapper exists (e.g. src/basic/macro.h wrapping
src/fundamental/macro.h), the wrapper uses an explicit "../fundamental/foo.h"
or "../basic/foo.h" relative include for the lower-tier header. We can't use
GCC's #include_next directive for this — when the wrapper is reachable both
via same-dir-as-source lookup and via -I (e.g. -Isrc/shared) for the
directory it lives in, #include_next advances by exactly one slot in libcpp's
internal directory chain and lands on the same physical directory it was
already in, never reaching the lower-tier sibling (see make_cpp_dir() in
gcc/libcpp/files.cc:1986).
To make sure the right headers are always picked up, the include directories
are reordered so that e.g. src/shared always takes priority over src/basic and
similar for the other directories.
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
UEFI firmware can report the currently-active keyboard layout via
EFI_HII_DATABASE_PROTOCOL.GetKeyboardLayout(). The layout descriptor
includes an RFC 4646 / BCP 47 language tag (e.g. "en-US"). Query this
from sd-boot/sd-stub and write it to a new LoaderKeyboardLayout EFI
variable, advertised through a new EFI_LOADER_FEATURE_KEYBOARD_LAYOUT
feature bit.
On the OS side, systemd-vconsole-setup reads the variable as a
lowest-priority fallback for the console keymap. To map the BCP 47
tag to a vconsole keymap we extend /usr/share/systemd/kbd-model-map
with an optional sixth column listing the comma-separated BCP 47 tags
each row covers; a new find_vconsole_keymap_for_bcp47() helper walks
the file, preferring an exact tag match and otherwise falling back to
the row whose tag matches the input's primary subtag. Credentials,
/etc/vconsole.conf, and vconsole.keymap= on the kernel command line
continue to take precedence.
bootctl status surfaces the new variable, printing the language tag
or "n/a (not reported by firmware)" when sd-boot advertises the
feature but the firmware HII database didn't expose a layout (common
on QEMU without a USB keyboard, since EDK2's PS/2 driver does not
register an HII keyboard layout).
In https://gcc.gnu.org/PR121911 `gcc` started enforcing the type of
`__stack_chk_guard` to `uintptr_t` and broke `systemd` build as:
```
../src/boot/efi-log.c:136:17: error: conflicting types for '__stack_chk_guard'; have 'intptr_t' {aka 'long int'}
136 | _used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3;
| ^~~~~~~~~~~~~~~~~
cc1: note: previous declaration of '__stack_chk_guard' with type 'long unsigned int'
../src/boot/efi-log.c:136:17: error: declaration of '__stack_chk_guard' shadows a global declaration [-Werror=shadow]
136 | _used_ intptr_t __stack_chk_guard = (intptr_t) 0x70f6967de78acae3;
| ^~~~~~~~~~~~~~~~~
```
Let's match the declaration to unsigned type as suggested by upstream in
https://gcc.gnu.org/PR121911#c6.
When the source buffer is NULL, the firmware is supposed to try to load the UKI
with simple filesystem protocol then load file 2 protocol. But it seems
on some versions of AMI, it does not use simple filesystem protocol,
and then fails to load if the ESP was loaded from an El Torito boot
catalog. Trying to load the source buffer from the simple filesystem protocol
protocols seems work around this limitation.
Shim for example, also loads the source buffer before calling LoadImage. So it
seems to be a safe thing to do. We could also maybe in the future use load file
2 protocol if simple filesystem failed in the first place.
This generates on-the-fly cpio initrds from 'extra' resources declared
in Type #1 entries and installs them via the Linux initrd protocol so
that they get passed to the Linux kernel.
Replaces: #39286
The PR to measure into is closely associated with where we place a
resource in the initrd cpios. Hence, let's also track it in CpioTarget,
thus simplifying our function parameter lists that way.
No change in behaviour.
There are only a few target dirs we place resources in when generating
on-the-fly initrd cpios. These dirs have very specific attributes.
Instead of repeating this everywhere, let's encapsulate them in a new
explicit structure, that we can reuse at various places.
This is preparation for placing extra resources of Type #1 entry also in
them without having to encode access modes at multiple places
redundantly.
This changes the initrd combination logic to also include any initrd
already configured via the "LINUX_INITRD_MEDIA_GUID" device in the
initrd we pass to the linux kernel.
Or in other words: with this systemd-stub starts operating purely
incremental: it will extend any previously installed initrd with its own
stuff, so that both the previous initrd(s) and systemd-stub's are in
effect.
So far, if an initrd is already registered we'd silently not register
one again. Let's make this more reliable and systematic, and register
ours, overriding what is previously set.
(Note, in a later commit we'll incorporate any previously set initrd,
which hence makes this all incremental instead of destructive as it
might appear now)
LoadFile() with a NULL buffer is supposed to return the file size
without acquiring the data and return EFI_BUFFER_TOO_SMALL.
However it appears some firmware returns EFI_SUCCESS in case the file is
empty, i.e. the file size returned is zero. And I guess that's even
fine.
Let's handle this gracefully hence.
When figuring out which menu entry to pick by default, let's not
consider any with a profile number > 0. This reflects that fact that
additional profiles are generally used for
debug/recovery/factory-reset/storage target mode boots, and those should
never be auto-selected. Hence do a simple check: if profile != 0, simply
do not consider the entry as a default.
We might eventually want to beef this up, and add a property one can set
in the profile metadata that controls this behaviour, but for now let's
just do a this simple fix.
The loop used the byte offset end (struct_off + struct_size) as the
iteration limit, but cursor[i] indexes uint32_t words. This reads
past the struct block when end > size_words.
Use size_words (struct_size / sizeof(uint32_t)) which is the correct
number of words to iterate over.
Also fix a pre-existing OOB in the FDT_BEGIN_NODE handler: the guard
i >= size_words is always false inside the loop (since the loop
condition already ensures i < size_words), so cursor[++i] at the
boundary reads one word past the struct block. Use i + 1 >= size_words
to check before incrementing.
Fixes: https://github.com/systemd/systemd/issues/41590
Bound image dimensions before computing row_size to prevent overflow
in the depth * x multiplication on 32-bit. Without this, crafted
dimensions like depth=32 x=0x10000001 wrap to a small row_size that
passes all subsequent checks.
Reject channel masks where all bits are set (popcount == 32), since
1U << 32 is undefined behavior and causes division by zero on
architectures where it evaluates to zero. Move the validation before
computing derived values for clarity. Use unsigned 1U in shifts to
avoid signed integer overflow UB for popcount == 31.
Also reject zero-width and zero-height images.
Fixes: https://github.com/systemd/systemd/issues/41589
Move the push-based streaming compression API from import-compress.c
into compress.c and delete import-compress.c/h. This consolidates all
compression code in one place and makes all compression libraries
(liblzma, liblz4, libzstd, libz, libbz2) runtime-loaded via dlopen
instead of directly linked.
Introduce opaque Compressor/Decompressor types backed by a heap-
allocated struct defined only in compress.c, keeping all third-party
library headers out of compress.h.
Rewrite the per-codec fd-to-fd stream functions as thin wrappers around
the push API via generic compress_stream()/decompress_stream() taking a
Compression type parameter. Integrate LZ4 into this framework using the
LZ4 Frame API, eliminating all LZ4 special-casing.
Extend the Compression enum with COMPRESSION_GZIP and COMPRESSION_BZIP2
and add the corresponding blob, startswith, and stream functions for
both.
Rename the ImportCompress types and functions: ImportCompressType becomes
the existing Compression enum, ImportCompress becomes Compressor (with
Decompressor typedef), and all import_compress_*/import_uncompress_*
become compressor_*/decompressor_*. Rename dlopen_lzma() to dlopen_xz()
for consistency. Make compression_to_string() return lowercase by
default.
Add INT_MAX/UINT_MAX overflow checks for LZ4, zlib, and bzip2 blob
functions where the codec API uses narrower integer types than our
uint64_t parameters.
Migrate test-compress.c and test-compress-benchmark.c to the TEST()
macro framework, new assertion macros, and codec-generic loops instead
of per-codec duplication.
Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
Per UEFI specification §13.3.2, El Torito partition discovery applies to
any block device, not just optical media. Rename
disk_get_part_uuid_cdrom() to disk_get_part_uuid_eltorito() and update
all log messages and comments to say "El Torito" instead of "CDROM" to
reflect this.