Merge pull request #13137 from poettering/efi-random

beef up random seed logic, add boot loader entropy privisioning, improve docs about it
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2019-07-26 12:43:32 +02:00 committed by GitHub
commit 47685d9d4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2239 additions and 214 deletions

14
TODO
View File

@ -4,11 +4,6 @@ Bugfixes:
manager or system manager can be always set. It would be better to reject
them when parsing config.
* busctl --user call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager GetUnitProcesses "s" run-rbff1b85427b34ba3adf864281aeda8e7.service
Failed to set address: No such file or directory
→ improve error message
External:
* Fedora: add an rpmlint check that verifies that all unit files in the RPM are listed in %systemd_post macros.
@ -42,7 +37,14 @@ Features:
that are linked to these places instead of copied. After all they are
constant vendor data.
* seed: check if first-boot and then don't do anything
* maybe add kernel cmdline params: 1) to force first-boot mode + 2) to force
random seed crediting
* nspawn: on cgroupsv1 issue cgroup empty handler process based on host events,
so that we make cgroup agent logic safe
* nspawn/machined: add API to invoke binary in container, then use that as
fallback in "machinectl shell"
* logind: rework pam_logind to also do a bus call in case of invocation from
user@.service, which returns the XDG_RUNTIME_DIR value, and make this

View File

@ -70,6 +70,28 @@ variables. All EFI variables use the vendor UUID
* `1 << 2` → The boot loader honours `LoaderEntryDefault` when set.
* `1 << 3` → The boot loader honours `LoaderEntryOneShot` when set.
* `1 << 4` → The boot loader supports boot counting as described in [Automatic Boot Assessment](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT).
* `1 << 5` → The boot loader supports looking for boot menu entries in the Extended Boot Loader Partition.
* `1 << 6` → The boot loader spports passing a random seed to the OS.
* The EFI variable `LoaderRandomSeed` contains a binary random seed if set. It
is set by the boot loader to pass an entropy seed read from the ESP partition
to the OS. The system manager then credits this seed to the kernel's entropy
pool. It is the responsibility of the boot loader to ensure the quality and
integrity of the random seed.
* The EFI variable `LoaderSystemToken` contains binary random data,
persistently set by the OS installer. Boot loaders that support passing
random seeds to the OS should use this data and combine it with the random
seed file read from the ESP. By combining this random data with the random
seed read off the disk before generating a seed to pass to the OS and a new
seed to store in the ESP the boot loader can protect itself from situations
where "golden" OS images that include a random seed are replicated and used
on multiple systems. Since the EFI variable storage is usually independent
(i.e. in physical NVRAM) of the ESP file system storage, and only the latter
is part of "golden" OS images, this ensures that different systems still come
up with different random seeds. Note that the `LoaderSystemToken` is
generally only written once, by the OS installer, and is usually not touched
after that.
If `LoaderTimeInitUSec` and `LoaderTimeExecUSec` are set, `systemd-analyze`
will include them in its boot-time analysis. If `LoaderDevicePartUUID` is set,
@ -77,7 +99,9 @@ systemd will mount the ESP that was used for the boot to `/boot`, but only if
that directory is empty, and only if no other file systems are mounted
there. The `systemctl reboot --boot-loader-entry=…` and `systemctl reboot
--boot-loader-menu=…` commands rely on the `LoaderFeatures` ,
`LoaderConfigTimeoutOneShot`, `LoaderEntries`, `LoaderEntryOneShot` variables.
`LoaderConfigTimeoutOneShot`, `LoaderEntries`, `LoaderEntryOneShot`
variables. `LoaderRandomSeed` is read by PID during early boot and credited to
the kernel's random pool.
## Boot Loader Entry Identifiers

418
docs/RANDOM_SEEDS.md Normal file
View File

@ -0,0 +1,418 @@
---
title: Random Seeds
---
# Random Seeds
systemd can help in a number of ways with providing reliable, high quality
random numbers from early boot on.
## Linux Kernel Entropy Pool
Today's computer systems require random number generators for numerous
cryptographic and other purposes. On Linux systems, the kernel's entropy pool
is typically used as high-quality source of random numbers. The kernel's
entropy pool combines various entropy inputs together, mixes them and provides
an API to userspace as well as to internal kernel subsystems to retrieve
it. This entropy pool needs to be initialized with a minimal level of entropy
before it can provide high quality, cryptographic random numbers to
applications. Until the entropy pool is fully initialized application requests
for high-quality random numbers cannot be fulfilled.
The Linux kernel provides three relevant userspace APIs to request random data
from the kernel's entropy pool:
* The [`getrandom()`](http://man7.org/linux/man-pages/man2/getrandom.2.html)
system call with its `flags` parameter set to 0. If invoked the calling
program will synchronously block until the random pool is fully initialized
and the requested bytes can be provided.
* The `getrandom()` system call with its `flags` parameter set to
`GRND_NONBLOCK`. If invoked the request for random bytes will fail if the
pool is not initialized yet.
* Reading from the
[`/dev/urandom`](http://man7.org/linux/man-pages/man4/urandom.4.html)
pseudo-device will always return random bytes immediately, even if the pool
is not initialized. The provided random bytes will be of low quality in this
case however. Moreover the kernel will log about all programs using this
interface in this state, and which thus potentially rely on an uninitialized
entropy pool.
(Strictly speaking there are more APIs, for example `/dev/random`, but these
should not be used by almost any application and hence aren't mentioned here.)
Note that the time it takes to initialize the random pool may differ between
systems. If local hardware random number generators are available,
initialization is likely quick, but particularly in embedded and virtualized
environments available entropy is small and thus random pool initialization
might take a long time (up to tens of minutes!).
Modern hardware tends to come with a number of hardware random number
generators (hwrng), that may be used to relatively quickly fill up the entropy
pool. Specifically:
* All recent Intel and AMD CPUs provide the CPU opcode
[RDRAND](https://en.wikipedia.org/wiki/RdRand) to acquire random bytes. Linux
includes random bytes generated this way in its entropy pool, but didn't use
to credit entropy for it (i.e. data from this source wasn't considered good
enough to consider the entropy pool properly filled even though it was
used). This has changed recently however, and most big distributions have
turned on the `CONFIG_RANDOM_TRUST_CPU=y` kernel compile time option. This
means systems with CPUs supporting this opcode will be able to very quickly
reach the "pool filled" state.
* The TPM security chip that is available on all modern desktop systems has a
hwrng. It is also fed into the entropy pool, but generally not credited
entropy. You may use `rng_core.default_quality=1000` on the kernel command
line to change that, but note that this is a global setting affect all
hwrngs. (Yeah, that's weird.)
* Many Intel and AMD chipsets have hwrng chips. Their Linux drivers usually
don't credit entropy. (But there's `rng_core.default_quality=1000`, see
above.)
* Various embedded boards have hwrng chips. Some drivers automatically credit
entropy, others do not. Some WiFi chips appear to have hwrng sources too, and
they usually do not credit entropy for them.
* `virtio-rng` is used in virtualized environments and retrieves random data
from the VM host. It credits full entropy.
* The EFI firmware typically provides a RNG API. When transitioning from UEFI
to kernel mode Linux will query some random data through it, and feed it into
the pool, but not credit entropy to it. What kind of random source is behind
the EFI RNG API is often not entirely clear, but it hopefully is some kind of
hardware source.
If neither of these are available (in fact, even if they are), Linux generates
entropy from various non-hwrng sources in various subsystems, all of which
ultimately are rooted in IRQ noise, a very "slow" source of entropy, in
particular in virtualized environments.
## `systemd`'s Use of Random Numbers
systemd is responsible for bringing up the OS. It generally runs as the first
userspace process the kernel invokes. Because of that it runs at a time where
the entropy pool is typically not yet initialized, and thus requests to acquire
random bytes will either be delayed, will fail or result in a noisy kernel log
message (see above).
Various other components run during early boot that require random bytes. For
example, initial RAM disks nowadays communicate with encrypted networks or
access encrypted storage which might need random numbers. systemd itself
requires random numbers as well, including for the following uses:
* systemd assigns 'invocation' UUIDs to all services it invokes that uniquely
identify each invocation. This is useful retain a global handle on a specific
service invocation and relate it to other data. For example, log data
collected by the journal usually includes the invocation UUID and thus the
runtime context the service manager maintains can be neatly matched up with
the log data a specific service invocation generated. systemd also
initializes `/etc/machine-id` with a randomized UUID. (systemd also makes use
of the randomized "boot id" the kernel exposes in
`/proc/sys/kernel/random/boot_id`). These UUIDs are exclusively Type 4 UUIDs,
i.e. randomly generated ones.
* systemd maintains various hash tables internally. In order to harden them
against [collision
attacks](https://rt.perl.org/Public/Bug/Display.html?CSRF_Token=165691af9ddaa95f653402f1b68de728)
they are seeded with random numbers.
* At various places systemd needs random bytes for temporary file name
generation, UID allocation randomization, and similar.
* systemd-resolved and systemd-networkd use random number generators to harden
the protocols they implement against packet forgery.
* systemd-udevd and systemd-nspawn can generate randomized MAC addresses for
network devices.
Note that these cases generally do not require a cryptographic-grade random
number generator, as most of these utilize random numbers to minimize risk of
collision and not to generate secret key material. However, they usually do
require "medium-grade" random data. For example: systemd's hash-maps are
reseeded if they grow beyond certain thresholds (and thus collisions are more
likely). This means they are generally fine with low-quality (even constant)
random numbers initially as long as they get better with time, so that
collision attacks are eventually thwarted as better, non-guessable seeds are
acquired.
## Keeping `systemd'`s Demand on the Kernel Entropy Pool Minimal
Since most of systemd's own use of random numbers do not require
cryptographic-grade RNGs, it tries to avoid reading entropy from the kernel
entropy pool if possible. If it succeeds this has the benefit that there's no
need to delay the early boot process until entropy is available, and noisy
kernel log messages about early reading from `/dev/urandom` are avoided
too. Specifically:
1. When generating [Type 4
UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_\(random\)),
systemd tries to use Intel's and AMD's RDRAND CPU opcode directly, if
available. While some doubt the quality and trustworthiness of the entropy
provided by these opcodes, they should be good enough for generating UUIDs,
if not key material (though, as mentioned, today's big distributions opted
to trust it for that too, now, see above — but we are not going to make that
decision for you, and for anything key material related will only use the
kernel's entropy pool). If RDRAND is not available or doesn't work, it will
use synchronous `getrandom()` as fallback, and `/dev/urandom` on old kernels
where that system call doesn't exist yet. This means on non-Intel/AMD
systems UUID generation will block on kernel entropy initialization.
2. For seeding hash tables, and all the other similar purposes systemd first
tries RDRAND, and if that's not available will try to use asynchronous
`getrandom()` (if the kernel doesn't support this system call,
`/dev/urandom` is used). This may fail too in case the pool is not
initialized yet, in which case it will fall back to glibc's internal rand()
calls, i.e. weak pseudo-random numbers. This should make sure we use good
random bytes if we can, but neither delay boot nor trigger noisy kernel log
messages during early boot for these use-cases.
## `systemd`'s Support for Filling the Kernel Entropy Pool
systemd has various provisions to ensure the kernel entropy is filled during
boot, in order to ensure the entropy pool is filled up quickly.
1. When systemd's PID 1 detects it runs in a virtualized environment providing
the `virtio-rng` interface it will load the necessary kernel modules to make
use of it during earliest boot, if possible — much earlier than regular
kernel module loading done by `systemd-udevd.service`. This should ensure
that in VM environments the entropy pool is quickly filled, even before
systemd invokes the first service process — as long as the VM environment
provides virtualized RNG hardware (and VM environments really should!).
2. The
[`systemd-random-seed.service`](https://www.freedesktop.org/software/systemd/man/systemd-random-seed.service.html)
system service will load a random seed from `/var/lib/systemd/random-seed`
into the kernel entropy pool. By default it does not credit entropy for it
though, since the seed is — more often than not — not reset when 'golden'
master images of an OS are created, and thus replicated into every
installation. If OS image builders carefully reset the random seed file
before generating the image it should be safe to credit entropy, which can
be enabled by setting the `$SYSTEMD_RANDOM_SEED` environment variable for
the service to `1`. Note however, that this service typically runs
relatively late during early boot: long after the initial RAM disk
(`initrd`) completed, and after the `/var/` file system became
writable. This is usually too late for many applications, it is hence not
advised to rely exclusively on this functionality to seed the kernel's
entropy pool. Also note that this service synchronously waits until the
kernel's entropy pool is initialized before completing start-up. It may thus
be used by other services as synchronization point to order against, if they
require an initialized entropy pool to operate correctly.
3. The
[`systemd-boot`](https://www.freedesktop.org/software/systemd/man/systemd-boot.html)
EFI boot loader included in systemd is able to maintain and provide a random
seed stored in the EFI System Partition (ESP) to the booted OS, which allows
booting up with a fully initialized entropy pool from earliest boot
on. During installation of the boot loader (or when invoking [`bootctl
random-seed`](https://www.freedesktop.org/software/systemd/man/bootctl.html#random-seed))
a seed file with an initial seed is placed in a file `/loader/random-seed`
in the ESP. In addition, an identically sized randomized EFI variable called
the the 'system token' is set, which is written to the machine's firmware
NVRAM. During boot, when `systemd-boot` finds both the random seed file and
the system token they are combined and hashed with SHA256 (in counter mode,
to generate sufficient data), to generate a new random seed file to store in
the ESP as well as a random seed to pass to the OS kernel. The new random
seed file for the ESP is then written to the ESP, ensuring this is completed
before the OS is invoked. Very early during initialization PID 1 will read
the random seed provided in the EFI variable and credit it fully to the
kernel's entropy pool.
This mechanism is able to safely provide an initialized entropy pool already
in the `initrd` and guarantees that different seeds are passed from the boot
loader to the OS on every boot (in a way that does not allow regeneration of
an old seed file from a new seed file). Moreover, when an OS image is
replicated between multiple images and the random seed is not reset, this
will still result in different random seeds being passed to the OS, as the
per-machine 'system token' is specific to the physical host, and not
included in OS disk images. If the 'system token' is properly initialized
and kept sufficiently secret it should not be possible to regenerate the
entropy pool of different machines, even if this seed is the only source of
entropy.
Note that the writes to the ESP needed to maintain the random seed should be
minimal. The size of the random seed file is directly derived from the Linux
kernel's entropy pool size, which defaults to 512 bytes. This means updating
the random seed in the ESP should be doable safely with a single sector
write (since hard-disk sectors typically happen to be 512 bytes long, too),
which should be safe even with FAT file system drivers built into
low-quality EFI firmwares.
As a special restriction: in virtualized environments PID 1 will refrain
from using this mechanism, for safety reasons. This is because on VM
environments the EFI variable space and the disk space is generally not
maintained physically separate (for example, `qemu` in EFI mode stores the
variables in the ESP itself). The robustness towards sloppy OS image
generation is the main purpose of maintaining the 'system token' however,
and if the EFI variable storage is not kept physically separate from the OS
image there's no point in it. That said, OS builders that know that they are
not going to replicate the built image on multiple systems may opt to turn
off the 'system token' concept by setting `random-seed-mode always` in the
ESP's
[`/loader/loader.conf`](https://www.freedesktop.org/software/systemd/man/loader.conf.html)
file. If done, `systemd-boot` will use the random seed file even if no
system token is found in EFI variables.
With the three mechanisms described above it should be possible to provide
early-boot entropy in most cases. Specifically:
1. On EFI systems, `systemd-boot`'s random seed logic should make sure good
entropy is available during earliest boot — as long as `systemd-boot` is
used as boot loader, and outside of virtualized environments.
2. On virtualized systems, the early `virtio-rng` hookup should ensure entropy
is available early on — as long as the VM environment provides virtualized
RNG devices, which they really should all do in 2019. Complain to your
hosting provider if they don't.
3. On Intel/AMD systems systemd's own reliance on the kernel entropy pool is
minimal (as RDRAND is used on those for UUID generation). This only works if
the CPU has RDRAND of course, which most physical CPUs do (but I hear many
virtualized CPUs do not. Pity.)
4. In all other cases, `systemd-random-seed.service` will help a bit, but — as
mentioned — is too late to help with early boot.
This primarily leaves two kind of systems in the cold:
1. Some embedded systems. Many embedded chipsets have hwrng functionality these
days. Consider using them while crediting
entropy. (i.e. `rng_core.default_quality=1000` on the kernel command line is
your friend). Or accept that the system might take a bit longer to
boot. Alternatively, consider implementing a solution similar to
systemd-boot's random seed concept in your platform's boot loader.
2. Virtualized environments that lack both virtio-rng and RDRAND. Tough
luck. Talk to your hosting provider, and ask them to fix this.
3. Also note: if you deploy an image without any random seed and/or without
installing any 'system token' in an EFI variable, as described above, this
means that on the first boot no seed can be passed to the OS
either. However, as the boot completes (with entropy acquired elsewhere),
systemd will automatically install both a random seed in the GPT and a
'system token' in the EFI variable space, so that any future boots will have
entropy from earliest boot on — all provided `systemd-boot` is used.
## Frequently Asked Questions
1. *Why don't you just use getrandom()? That's all you need!*
Did you read any of the above? getrandom() is hooked to the kernel entropy
pool, and during early boot it's not going to be filled yet, very likely. We
do use it in many cases, but not in all. Please read the above again!
2. *Why don't you use
[getentropy()](http://man7.org/linux/man-pages/man3/getentropy.3.html)? That's
all you need!*
Same story. That call is just a different name for `getrandom()` with
`flags` set to zero, and some additional limitations, and thus it also needs
the kernel's entropy pool to be initialized, which is the whole problem we
are trying to address here.
3. *Why don't you generate your UUIDs with
[`uuidd`](http://man7.org/linux/man-pages/man8/uuidd.8.html)? That's all you
need!*
First of all, that's a system service, i.e. something that runs as "payload"
of systemd, long after systemd is already up and hence can't provide us
UUIDs during earliest boot yet. Don't forget: to assign the invocation UUID
for the `uuidd.service` start we already need a UUID that the service is
supposed to provide us. More importantly though, `uuidd` needs state/a random
seed/a MAC address/host ID to operate, all of which are not available during
early boot.
4. *Why don't you generate your UUIDs with `/proc/sys/kernel/random/uuid`?
That's all you need!*
This is just a different, more limited interface to `/dev/urandom`. It gains
us nothing.
5. *Why don't you use [`rngd`](https://github.com/nhorman/rng-tools),
[`haveged`](http://www.issihosts.com/haveged/),
[`egd`](http://egd.sourceforge.net/)? That's all you need!*
Like `uuidd` above these are system services, hence come too late for our
use-case. In addition much of what `rngd` provides appears to be equivalent
to `CONFIG_RANDOM_TRUST_CPU=y` or `rng_core.default_quality=1000`, except
being more complex and involving userspace. These services partly measure
system behavior (such as scheduling effects) which the kernel either
already feeds into its pool anyway (and thus shouldn't be fed into it a
second time, crediting entropy for it a second time) or is at least
something the kernel could much better do on its own. Hence, if what these
daemons do is still desirable today, this would be much better implemented
in kernel (which would be very welcome of course, but wouldn't really help
us here in our specific problem, see above).
6. *Why don't you use [`arc4random()`](https://man.openbsd.org/arc4random.3)?
That's all you need!*
This doesn't solve the issue, since it requires a nonce to start from, and
it gets that from `getrandom()`, and thus we have to wait for random pool
initialization the same way as calling `getrandom()`
directly. `arc4random()` is nothing more than optimization, in fact it
implements similar algorithms that the kernel entropy pool implements
anyway, hence besides being able to provide random bytes with higher
throughput there's little it gets us over just using `getrandom()`. Also,
it's not supported by glibc. And as long as that's the case we are not keen
on using it, as we'd have to maintain that on our own, and we don't want to
maintain our own cryptographic primitives if we don't have to. Since
systemd's uses are not performance relevant (besides the pool initialization
delay, which this doesn't solve), there's hence little benefit for us to
call these functions. That said, if glibc learns these APIs one day, we'll
certainly make use of them where appropriate.
7. *This is boring: NetBSD had [boot loader entropy seed
support](https://netbsd.gw.com/cgi-bin/man-cgi?boot+8) since ages!*
Yes, NetBSD has that, and the above is inspired by that (note though: this
article is about a lot more than that). NetBSD's support is not really safe,
since it neither updates the random seed before using it, nor has any
safeguards against replicating the same disk image with its random seed on
multiple machines (which the 'system token' mentioned above is supposed to
address). This means reuse of the same random seed by the boot loader is
much more likely.
8. *Why does PID 1 upload the boot loader provided random seed into kernel
instead of kernel doing that on its own?*
That's a good question. Ideally the kernel would do that on its own, and we
wouldn't have to involve userspace in this.
9. *What about non-EFI?*
The boot loader random seed logic described above uses EFI variables to pass
the seed from the boot loader to the OS. Other systems might have similar
functionality though, and it shouldn't be too hard to implement something
similar for them. Ideally, we'd have an official way to pass such a seed as
part of the `struct boot_params` from the boot loader to the kernel, but
this is currently not available.
10. *I use a different boot loader than `systemd-boot`, I'd like to use boot
loader random seeds too!*
Well, consider just switching to `systemd-boot`, it's worth it. See
[systemd-boot(7)](https://www.freedesktop.org/software/systemd/man/systemd-boot.html)
for an introduction why. That said, any boot loader can re-implement the
logic described above, and can pass a random seed that systemd as PID 1
will then upload into the kernel's entropy pool. For details see the [Boot
Loader Interface](https://systemd.io/BOOT_LOADER_INTERFACE) documentation.
11. *Why not pass the boot loader random seed via kernel command line instead
of as EFI variable?*
The kernel command line is accessible to unprivileged processes via
`/proc/cmdline`. It's not desirable if unprivileged processes can use this
information to possibly gain too much information about the current state
of the kernel's entropy pool.
12. *Why doesn't `systemd-boot` rewrite the 'system token' too each time
when updating the random seed file stored in the ESP?*
The system token is stored as persistent EFI variable, i.e. in some form of
NVRAM. These memory chips tend be of low quality in many machines, and
hence we shouldn't write them too often. Writing them once during
installation should generally be OK, but rewriting them on every single
boot would probably wear the chip out too much, and we shouldn't risk that.

View File

@ -45,15 +45,15 @@
<varlistentry>
<term><option>--esp-path=</option></term>
<listitem><para>Path to the EFI System Partition (ESP). If not specified, <filename>/efi/</filename>,
<filename>/boot/</filename>, and <filename>/boot/efi</filename> are checked in turn. It is recommended to mount
the ESP to <filename>/efi/</filename>, if possible.</para></listitem>
<filename>/boot/</filename>, and <filename>/boot/efi/</filename> are checked in turn. It is
recommended to mount the ESP to <filename>/efi/</filename>, if possible.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--boot-path=</option></term>
<listitem><para>Path to the Extended Boot Loader partition, as defined in the <ulink
url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>. If not
specified, <filename>/boot/</filename> are checked. It is recommended to mount the Extended Boot
specified, <filename>/boot/</filename> is checked. It is recommended to mount the Extended Boot
Loader partition to <filename>/boot/</filename>, if possible.</para></listitem>
</varlistentry>
@ -70,8 +70,17 @@
<listitem><para>This option modifies the behaviour of <command>status</command>. Only prints the path
to the Extended Boot Loader partition if it exists, and the path to the ESP otherwise to standard
output and exit. This command is useful to determine where to place boot loader entries, as they are
preferably placed in the Extended Boot Loader partition if it exists and in the ESP otherwise.
</para></listitem>
preferably placed in the Extended Boot Loader partition if it exists and in the ESP otherwise.</para>
<para>Boot Loader Specification Type #1 entries should generally be placed in the directory
<literal>$(bootctl -x)/loader/entries/</literal>. Existence of that directory may also be used as
indication that boot loader entry support is available on the system. Similarly, Boot Loader
Specification Type #2 entries should be placed in the directory <literal>$(bootctl
-x)/EFI/Linux/</literal>.</para>
<para>Note that this option (similar to the <option>--print-booth-path</option> option mentioned
above), is available independently from the boot loader used, i.e. also without
<command>systemd-boot</command> being installed.</para></listitem>
</varlistentry>
<varlistentry>
@ -124,6 +133,31 @@
and the firmware's boot loader list.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>random-seed</option></term>
<listitem><para>Generates a random seed and stores it in the EFI System Partition, for use by the
<command>systemd-boot</command> boot loader. Also, generates a random 'system token' and stores it
persistently as an EFI variable, if one has not been set before. If the boot loader finds the random
seed in the ESP and the system token in the EFI variable it will derive a random seed to pass to the
OS and a new seed to store in the ESP from the combination of both. The random seed passed to the OS
is credited to the kernel's entropy pool by the system manager during early boot, and permits
userspace to boot up with an entropy pool fully initialized very early on. Also see
<citerefentry><refentrytitle>systemd-boot-system-token.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<para>See <ulink url="https://systemd.io/RANDOM_SEEDS">Random Seeds</ulink> for further
information.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>is-installed</option></term>
<listitem><para>Checks whether <command>systemd-boot</command> is installed in the ESP. Note that a
single ESP might host multiple boot loaders; this hence checks whether
<command>systemd-boot</command> is one (of possibly many) installed boot loaders — and neither
whether it is the default nor whether it is registered in any EFI variables.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>list</option></term>
@ -165,7 +199,8 @@
<para>
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>,
<ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>
<ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>,
<citerefentry><refentrytitle>systemd-boot-system-token.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -153,6 +153,25 @@
<listitem><para>Takes a boolean argument. Enable (the default) or disable
the "Reboot into firmware" entry.</para></listitem>
</varlistentry>
<varlistentry>
<term>random-seed-mode</term>
<listitem><para>Takes one of <literal>off</literal>, <literal>with-system-token</literal> and
<literal>always</literal>. If <literal>off</literal> no random seed data is read off the ESP, nor
passed to the OS. If <literal>with-system-token</literal> (the default)
<command>systemd-boot</command> will read a random seed from the ESP (from the file
<filename>/loader/random-seed</filename>) only if the <varname>LoaderSystemToken</varname> EFI
variable is set, and then derive the random seed to pass to the OS from the combination. If
<literal>always</literal> the boot loader will do so even if <varname>LoaderSystemToken</varname> is
not set. This mode is useful in environments where protection against OS image reuse is not a
concern, and the random seed shall be used even with no further setup in place. User <command>bootctl
random-seed</command> to initialize both the random seed file in the ESP and the system token EFI
variable.</para>
<para>See <ulink url="https://systemd.io/RANDOM_SEEDS">Random Seeds</ulink> for further
information.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>

View File

@ -655,6 +655,7 @@ manpages = [
['systemd-bless-boot-generator', '8', [], 'ENABLE_EFI'],
['systemd-bless-boot.service', '8', [], 'ENABLE_EFI'],
['systemd-boot-check-no-failures.service', '8', [], ''],
['systemd-boot-system-token.service', '8', [], 'ENABLE_EFI'],
['systemd-boot', '7', ['sd-boot'], 'ENABLE_EFI'],
['systemd-cat', '1', [], ''],
['systemd-cgls', '1', [], ''],

View File

@ -0,0 +1,76 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1+ -->
<refentry id="systemd-boot-system-token.service" conditional='ENABLE_EFI'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-boot-system-token.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-boot-system-token.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-boot-system-token.service</refname>
<refpurpose>Generate an initial boot loader system token and random seed</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-boot-system-token.service</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-boot-system-token.service</filename> is a system service that automatically
generates a 'system token' to store in an EFI variable in the system's NVRAM and a random seed to store
on the EFI System Partition ESP on disk. The boot loader may then combine these two randomized data
fields by cryptographic hashing, and pass it to the OS it boots as initialization seed for its entropy
pool. The random seed stored in the ESP is refreshed on each reboot ensuring that multiple subsequent
boots will boot with different seeds. The 'system token' is generated randomly once, and then
persistently stored in the system's EFI variable storage.</para>
<para>The <filename>systemd-boot-system-token.service</filename> unit invokes the <command>bootctl
random-seed</command> command, which updates the random seed in the ESP, and initializes the 'system
token' if it's not initialized yet. The service is conditionalized so that it is run only when all of the
below apply:</para>
<itemizedlist>
<listitem><para>A boot loader is used that implements the <ulink
url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink> (which defines the 'system
token' concept).</para></listitem>
<listitem><para>Either a 'system token' was not set yet, or the boot loader has not passed the OS a
random seed yet (and thus most likely has been missing the random seed file in the
ESP).</para></listitem>
<listitem><para>The system is not running in a VM environment. This case is explicitly excluded since
on VM environments the ESP backing storage and EFI variable storage is typically not physically
separated and hence booting the same OS image in multiple instances would replicate both, thus reusing
the same random seed and 'system token' among all instances, which defeats its purpose. Note that it's
still possible to use boot loader random seed provisioning in this mode, but the automatic logic
implemented by this service has no effect then, and the user instead has to manually invoke the
<command>bootctl random-seed</command> acknowledging these restrictions.</para></listitem>
</itemizedlist>
<para>For further details see
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, regarding
the command this service invokes.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -28,13 +28,14 @@
manager. It provides a graphical menu to select the entry to boot and an editor for the kernel command
line. <command>systemd-boot</command> supports systems with UEFI firmware only.</para>
<para>systemd-boot loads boot entry information from the EFI system partition (ESP), usually mounted at
<filename>/efi/</filename>, <filename>/boot/</filename>, or <filename>/boot/efi/</filename> during OS
runtime, as well as from the Extended Boot Loader partition if it exists (usually mounted to
<filename>/boot/</filename>). Configuration file fragments, kernels, initrds and other EFI images to boot
generally need to reside on the ESP or the Extended Boot Loader partition. Linux kernels must be built
with <option>CONFIG_EFI_STUB</option> to be able to be directly executed as an EFI image. During boot
systemd-boot automatically assembles a list of boot entries from the following sources:</para>
<para><command>systemd-boot</command> loads boot entry information from the EFI system partition (ESP),
usually mounted at <filename>/efi/</filename>, <filename>/boot/</filename>, or
<filename>/boot/efi/</filename> during OS runtime, as well as from the Extended Boot Loader partition if
it exists (usually mounted to <filename>/boot/</filename>). Configuration file fragments, kernels,
initrds and other EFI images to boot generally need to reside on the ESP or the Extended Boot Loader
partition. Linux kernels must be built with <option>CONFIG_EFI_STUB</option> to be able to be directly
executed as an EFI image. During boot <command>systemd-boot</command> automatically assembles a list of
boot entries from the following sources:</para>
<itemizedlist>
<listitem><para>Boot entries defined with <ulink
@ -57,17 +58,50 @@
<listitem><para>A reboot into the UEFI firmware setup option, if supported by the firmware</para></listitem>
</itemizedlist>
<para><citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry>
may be used to copy kernel images onto the ESP or the Extended Boot Loader Partition and to generate
description files compliant with the Boot Loader
Specification. <citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
<para><command>systemd-boot</command> supports the following features:</para>
<itemizedlist>
<listitem><para>Basic boot manager configuration changes (such as timeout
configuration, default boot entry selection, …) may be made directly from the boot loader UI at
boot-time, as well as during system runtime with EFI variables.</para></listitem>
<listitem><para>The boot manager integrates with the <command>systemctl</command> command to implement
features such as <command>systemctl reboot --boot-loader-entry=…</command> (for rebooting into a
specific boot menu entry, i.e. "reboot into Windows") and <command>systemctl reboot
--boot-loader-menu=…</command> (for rebooting into the boot loader menu), by implementing the <ulink
url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>. See
<citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details.</para></listitem>
<listitem><para>An EFI variable set by the boot loader informs the OS about the ESP partition used
during boot. This is then used to automatically mount the correct ESP partition to
<filename>/efi/</filename> or <filename>/boot/</filename> during OS runtime. See
<citerefentry><refentrytitle>systemd-gpt-auto-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
for details.</para></listitem>
<listitem><para>The boot manager provides information about the boot time spent in UEFI firmware using
the <ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>. This
information can be displayed using
<citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
</para></listitem>
<listitem><para>The boot manager implements boot counting and automatic fallback to older, working boot
entries on failure. See <ulink url="https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT">Automatic Boot
Assessment</ulink>.</para></listitem>
<listitem><para>The boot manager optionally reads a random seed from the ESP partition, combines it
with a 'system token' stored in a persistant EFI variable and derives a random seed to use by the OS as
entropy pool initializaton, providing a full entropy pool during early boot.</para></listitem>
</itemizedlist>
<para><citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
may be used from a running system to locate the ESP and the Extended Boot Loader Partition, list
available entries, and install <command>systemd-boot</command> itself.</para>
<para>systemd-boot will provide information about the time spent in UEFI firmware using the <ulink
url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>. This information can be displayed
using <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
</para>
<para><citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry>
may be used to copy kernel images onto the ESP or the Extended Boot Loader Partition and to generate
description files compliant with the Boot Loader
Specification.</para>
</refsect1>
<refsect1>
@ -238,7 +272,9 @@
Loader Specification</ulink> are read from <filename>/loader/entries/</filename> on the ESP and the
Extended Boot Loader partition. Unified kernel boot entries following the <ulink
url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink> are read from
<filename>/EFI/Linux/</filename> on the ESP and the Extended Boot Loader partition.</para>
<filename>/EFI/Linux/</filename> on the ESP and the Extended Boot Loader partition. Optionally, a random
seed for early boot entropy pool provisioning is stored in <filename>/loader/random-seed</filename> in
the ESP.</para>
</refsect1>
<refsect1>
@ -346,10 +382,42 @@
<listitem><para>Information about the time spent in various parts of the boot loader. Set by the boot
loader. Use <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
to view this data. These variables are defined by the <ulink
url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>.</para></listitem>
to view this data. </para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderRandomSeed</varname></term>
<listitem><para>A binary random seed <command>systemd-boot</command> may optionally pass to the
OS. This is a volatile EFI variable that is hashed at boot from the combination of a random seed
stored in the ESP (in <filename>/loader/random-seed</filename>) and a "system token" persistently
stored in the EFI variable <varname>LoaderSystemToken</varname> (see below). During early OS boot the
system manager reads this variable and passes it to the OS kernel's random pool, crediting the full
entropy it contains. This is an efficient way to ensure the system starts up with a fully initialized
kernel random pool — as early as the initial RAM disk phase. <command>systemd-boot</command> reads
the random seed from the ESP, combines it with the "system token", and both derives a new random seed
to update in-place the seed stored in the ESP, and the random seed to pass to the OS from it via
SHA256 hashing in counter mode. This ensures that different physical systems that boot the same
"golden" OS image — i.e. containing the same random seed file in the ESP — will still pass a
different random seed to the OS. It is made sure the random seed stored in the ESP is fully
overwritten before the OS is booted, to ensure different random seed data is used between subsequent
boots.</para>
<para>See <ulink url="https://systemd.io/RANDOM_SEEDS">Random Seeds</ulink> for
further information.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>LoaderSystemToken</varname></term>
<listitem><para>A binary random data field, that is used for generating the random see to pass to the
OS (see above). Note that this random data is generally only generated once, during OS installation,
and is then never updated again.</para></listitem>
</varlistentry>
</variablelist>
<para>Many of these variables are defined by the <ulink
url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>.</para>
</refsect1>
<refsect1>
@ -413,6 +481,7 @@
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>loader.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-bless-boot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-boot-system-token.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<ulink url="https://systemd.io/BOOT_LOADER_SPECIFICATION">Boot Loader Specification</ulink>,
<ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>

View File

@ -29,21 +29,63 @@
<refsect1>
<title>Description</title>
<para><filename>systemd-random-seed.service</filename> is a
service that restores the random seed of the system at early boot
and saves it at shutdown. See
<citerefentry><refentrytitle>random</refentrytitle><manvolnum>4</manvolnum></citerefentry>
for details. Saving/restoring the random seed across boots
increases the amount of available entropy early at boot. On disk
the random seed is stored in
<filename>/var/lib/systemd/random-seed</filename>.</para>
<para><filename>systemd-random-seed.service</filename> is a service that loads an on-disk random seed
into the kernel entropy pool during boot and saves it at shutdown. See
<citerefentry><refentrytitle>random</refentrytitle><manvolnum>4</manvolnum></citerefentry> for
details. By default, no entropy is credited when the random seed is written into the kernel entropy pool,
but this may be changed with <varname>$SYSTEMD_RANDOM_SEED_CREDIT</varname>, see below. On disk the random
seed is stored in <filename>/var/lib/systemd/random-seed</filename>.</para>
<para>Note that this service runs relatively late during the early boot phase, i.e. generally after the
initial RAM disk (initrd) completed its work, and the <filename>/var/</filename> file system has been
mounted writable. Many system services require entropy much earlier than this — this service is hence of
limited use for complex system. It is recommended to use a boot loader that can pass an initial random
seed to the kernel to ensure that entropy is available from earliest boot on, for example
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>, with
its <command>bootctl random-seed</command> functionality.</para>
<para>When loading the random seed from disk its file is immediately updated with a new seed retrieved
from the kernel, in order to ensure no two boots operate with the same random seed. This new seed is
retrieved synchronously from the kernel, which means the service will not complete start-up until the
random pool is fully initialized. On entropy-starved systems this may take a while. This functionality is
intended to be used as synchronization point for ordering services that require an initialized entropy
pool to function securely (i.e. services that access <filename>/dev/urandom</filename> without any
further precautions).</para>
<para>Care should be taken when creating OS images that are replicated to multiple systems: if the random
seed file is included unmodified each system will initialize its entropy pool with the same data, and
thus — if otherwise entropy-starved — generate the same or at least guessable random seed streams. As a
safety precaution crediting entropy is thus disabled by default. It is recommended to remove the random
seed from OS images intended for replication on multiple systems, in which case it is safe to enable
entropy crediting, see below.</para>
<para>See <ulink url="https://systemd.io/RANDOM_SEEDS">Random Seeds</ulink> for further
information.</para>
</refsect1>
<refsect1>
<title>Environment</title>
<variablelist class='environment-variables'>
<varlistentry>
<term><varname>$SYSTEMD_RANDOM_SEED_CREDIT</varname></term>
<listitem><para>By default, <filename>systemd-random-seed.service</filename> does not credit any
entropy when loading the random seed. With this option this behaviour may be changed: it either takes
a boolean parameter or the special string <literal>force</literal>. Defaults to false, in which case
no entropy is credited. If true, entropy is credited if the random seed file and system state pass
various superficial concisistency checks. If set to <literal>force</literal> entropy is credited,
regardless of these checks, as long as the random seed file exists.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>random</refentrytitle><manvolnum>4</manvolnum></citerefentry>
<citerefentry><refentrytitle>random</refentrytitle><manvolnum>4</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>4</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -1298,6 +1298,17 @@ int fsync_directory_of_file(int fd) {
return 0;
}
int fsync_full(int fd) {
int r, q;
/* Sync both the file and the directory */
r = fsync(fd) < 0 ? -errno : 0;
q = fsync_directory_of_file(fd);
return r < 0 ? r : q;
}
int fsync_path_at(int at_fd, const char *path) {
_cleanup_close_ int opened_fd = -1;
int fd;

View File

@ -114,6 +114,7 @@ void unlink_tempfilep(char (*p)[]);
int unlinkat_deallocate(int fd, const char *name, int flags);
int fsync_directory_of_file(int fd);
int fsync_full(int fd);
int fsync_path_at(int at_fd, const char *path);
int syncfs_path(int atfd, const char *path);

View File

@ -25,8 +25,10 @@
#include "alloc-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "io-util.h"
#include "missing.h"
#include "parse-util.h"
#include "random-util.h"
#include "siphash24.h"
#include "time-util.h"
@ -389,3 +391,26 @@ void random_bytes(void *p, size_t n) {
/* If for some reason some user made /dev/urandom unavailable to us, or the kernel has no entropy, use a PRNG instead. */
pseudo_random_bytes(p, n);
}
size_t random_pool_size(void) {
_cleanup_free_ char *s = NULL;
int r;
/* Read pool size, if possible */
r = read_one_line_file("/proc/sys/kernel/random/poolsize", &s);
if (r < 0)
log_debug_errno(r, "Failed to read pool size from kernel: %m");
else {
unsigned sz;
r = safe_atou(s, &sz);
if (r < 0)
log_debug_errno(r, "Failed to parse pool size: %s", s);
else
/* poolsize is in bits on 2.6, but we want bytes */
return CLAMP(sz / 8, RANDOM_POOL_SIZE_MIN, RANDOM_POOL_SIZE_MAX);
}
/* Use the minimum as default, if we can't retrieve the correct value */
return RANDOM_POOL_SIZE_MIN;
}

View File

@ -31,3 +31,9 @@ static inline uint32_t random_u32(void) {
}
int rdrand(unsigned long *ret);
/* Some limits on the pool sizes when we deal with the kernel random pool */
#define RANDOM_POOL_SIZE_MIN 512U
#define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U)
size_t random_pool_size(void);

View File

@ -27,7 +27,7 @@ int getxattr_malloc(const char *path, const char *name, char **value, bool allow
assert(name);
assert(value);
for (l = 100; ; l = (size_t) n + 1) {
for (l = 100; ; l = (size_t) n + 1 /* extra byte to make sure this remains NUL suffixed */) {
v = new0(char, l);
if (!v)
return -ENOMEM;
@ -36,7 +36,6 @@ int getxattr_malloc(const char *path, const char *name, char **value, bool allow
n = lgetxattr(path, name, v, l);
else
n = getxattr(path, name, v, l);
if (n >= 0 && (size_t) n < l) {
*value = v;
return n;
@ -65,13 +64,12 @@ int fgetxattr_malloc(int fd, const char *name, char **value) {
assert(name);
assert(value);
for (l = 100; ; l = (size_t) n + 1) {
for (l = 100;; l = (size_t) n + 1 /* extra byte to make sure this remains NUL suffixed */) {
v = new0(char, l);
if (!v)
return -ENOMEM;
n = fgetxattr(fd, name, v, l);
if (n >= 0 && (size_t) n < l) {
*value = v;
return n;

View File

@ -25,6 +25,7 @@
#include "copy.h"
#include "dirent-util.h"
#include "efivars.h"
#include "env-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
@ -34,6 +35,7 @@
#include "pager.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "random-util.h"
#include "rm-rf.h"
#include "stat-util.h"
#include "stdio-util.h"
@ -580,21 +582,29 @@ static int mkdir_one(const char *prefix, const char *suffix) {
}
static const char *const esp_subdirs[] = {
/* The directories to place in the ESP */
"EFI",
"EFI/systemd",
"EFI/BOOT",
"loader",
/* Note that "/loader/entries" is not listed here, since it should be placed in $BOOT, which might
* not necessarily be the ESP */
NULL
};
static int create_esp_subdirs(const char *esp_path) {
static const char *const dollar_boot_subdirs[] = {
/* The directories to place in the XBOOTLDR partition or the ESP, depending what exists */
"loader",
"loader/entries", /* Type #1 entries */
"EFI",
"EFI/Linux", /* Type #2 entries */
NULL
};
static int create_subdirs(const char *root, const char * const *subdirs) {
const char *const *i;
int r;
STRV_FOREACH(i, esp_subdirs) {
r = mkdir_one(esp_path, *i);
STRV_FOREACH(i, subdirs) {
r = mkdir_one(root, *i);
if (r < 0)
return r;
}
@ -603,6 +613,7 @@ static int create_esp_subdirs(const char *esp_path) {
}
static int copy_one_file(const char *esp_path, const char *name, bool force) {
const char *e;
char *p, *q;
int r;
@ -610,13 +621,13 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) {
q = strjoina(esp_path, "/EFI/systemd/", name);
r = copy_file_with_version_check(p, q, force);
if (startswith(name, "systemd-boot")) {
e = startswith(name, "systemd-boot");
if (e) {
int k;
char *v;
/* Create the EFI default boot loader name (specified for removable devices) */
v = strjoina(esp_path, "/EFI/BOOT/BOOT",
name + STRLEN("systemd-boot"));
v = strjoina(esp_path, "/EFI/BOOT/BOOT", e);
ascii_strupper(strrchr(v, '/') + 1);
k = copy_file_with_version_check(p, v, force);
@ -650,7 +661,7 @@ static int install_binaries(const char *esp_path, bool force) {
return r;
}
static bool same_entry(uint16_t id, const sd_id128_t uuid, const char *path) {
static bool same_entry(uint16_t id, sd_id128_t uuid, const char *path) {
_cleanup_free_ char *opath = NULL;
sd_id128_t ouuid;
int r;
@ -864,19 +875,27 @@ static int rmdir_one(const char *prefix, const char *suffix) {
return 0;
}
static int remove_esp_subdirs(const char *esp_path) {
size_t i;
int r = 0;
static int remove_subdirs(const char *root, const char *const *subdirs) {
int r, q;
for (i = ELEMENTSOF(esp_subdirs)-1; i > 0; i--) {
int q;
/* We use recursion here to destroy the directories in reverse order. Which should be safe given how
* short the array is. */
q = rmdir_one(esp_path, esp_subdirs[i-1]);
if (q < 0 && r >= 0)
r = q;
}
if (!subdirs[0]) /* A the end of the list */
return 0;
return r;
r = remove_subdirs(root, subdirs + 1);
q = rmdir_one(root, subdirs[0]);
return r < 0 ? r : q;
}
static int remove_machine_id_directory(const char *root, sd_id128_t machine_id) {
char buf[SD_ID128_STRING_MAX];
assert(root);
return rmdir_one(root, sd_id128_to_string(machine_id, buf));
}
static int remove_binaries(const char *esp_path) {
@ -893,26 +912,22 @@ static int remove_binaries(const char *esp_path) {
return r;
}
static int remove_loader_config(const char *esp_path) {
static int remove_file(const char *root, const char *file) {
const char *p;
assert(esp_path);
assert(root);
assert(file);
p = prefix_roota(esp_path, "/loader/loader.conf");
p = prefix_roota(root, file);
if (unlink(p) < 0) {
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to unlink file \"%s\": %m", p);
if (errno != ENOENT)
return -errno;
} else
log_info("Removed \"%s\".", p);
log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
"Failed to unlink file \"%s\": %m", p);
return 0;
}
return errno == ENOENT ? 0 : -errno;
}
static int remove_entries_directory(const char *dollar_boot_path) {
assert(dollar_boot_path);
return rmdir_one(dollar_boot_path, "/loader/entries");
log_info("Removed \"%s\".", p);
return 1;
}
static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) {
@ -936,6 +951,35 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) {
return 0;
}
static int remove_loader_variables(void) {
const char *p;
int r = 0;
/* Remove all persistent loader variables we define */
FOREACH_STRING(p,
"LoaderConfigTimeout",
"LoaderConfigTimeoutOneShot",
"LoaderEntryDefault",
"LoaderEntryOneShot",
"LoaderSystemToken") {
int q;
q = efi_set_variable(EFI_VENDOR_LOADER, p, NULL, 0);
if (q == -ENOENT)
continue;
if (q < 0) {
log_warning_errno(q, "Failed to remove %s variable: %m", p);
if (r >= 0)
r = q;
} else
log_info("Removed EFI variable %s.", p);
}
return r;
}
static int install_loader_config(const char *esp_path, sd_id128_t machine_id) {
char machine_string[SD_ID128_STRING_MAX];
_cleanup_(unlink_and_freep) char *t = NULL;
@ -975,21 +1019,12 @@ static int install_loader_config(const char *esp_path, sd_id128_t machine_id) {
return 1;
}
static int install_entries_directories(const char *dollar_boot_path, sd_id128_t machine_id) {
int r;
static int install_machine_id_directory(const char *root, sd_id128_t machine_id) {
char buf[SD_ID128_STRING_MAX];
assert(dollar_boot_path);
assert(root);
/* Both /loader/entries and the entry directories themselves should be located on the same
* partition. Also create the parent directory for entry directories, so that kernel-install
* knows where to put them. */
r = mkdir_one(dollar_boot_path, "loader/entries");
if (r < 0)
return r;
return mkdir_one(dollar_boot_path, sd_id128_to_string(machine_id, buf));
return mkdir_one(root, sd_id128_to_string(machine_id, buf));
}
static int help(int argc, char *argv[], void *userdata) {
@ -1015,6 +1050,8 @@ static int help(int argc, char *argv[], void *userdata) {
" install Install systemd-boot to the ESP and EFI variables\n"
" update Update systemd-boot in the ESP and EFI variables\n"
" remove Remove systemd-boot from the ESP and EFI variables\n"
" random-seed Initialize random seed in ESP and EFI variables\n"
" is-installed Test whether systemd-boot is installed in the ESP\n"
"\nBoot Loader Entries Commands:\n"
" list List boot loader entries\n"
" set-default ID Set default boot loader entry\n"
@ -1154,12 +1191,13 @@ static int verb_status(int argc, char *argv[], void *userdata) {
uint64_t flag;
const char *name;
} flags[] = {
{ EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" },
{ EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" },
{ EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" },
{ EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" },
{ EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" },
{ EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" },
{ EFI_LOADER_FEATURE_BOOT_COUNTING, "Boot counting" },
{ EFI_LOADER_FEATURE_CONFIG_TIMEOUT, "Menu timeout control" },
{ EFI_LOADER_FEATURE_CONFIG_TIMEOUT_ONE_SHOT, "One-shot menu timeout control" },
{ EFI_LOADER_FEATURE_ENTRY_DEFAULT, "Default entry control" },
{ EFI_LOADER_FEATURE_ENTRY_ONESHOT, "One-shot entry control" },
{ EFI_LOADER_FEATURE_XBOOTLDR, "Support for XBOOTLDR partition" },
{ EFI_LOADER_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" },
};
_cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL;
@ -1212,6 +1250,22 @@ static int verb_status(int argc, char *argv[], void *userdata) {
printf(" ESP: n/a\n");
printf(" File: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), strna(loader_path));
printf("\n");
printf("Random Seed:\n");
printf(" Passed to OS: %s\n", yes_no(access("/sys/firmware/efi/efivars/LoaderRandomSeed-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f", F_OK) >= 0));
printf(" System Token: %s\n", access("/sys/firmware/efi/efivars/LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f", F_OK) >= 0 ? "set" : "not set");
if (arg_esp_path) {
_cleanup_free_ char *p = NULL;
p = path_join(arg_esp_path, "/loader/random-seed");
if (!p)
return log_oom();
printf(" Exists: %s\n", yes_no(access(p, F_OK) >= 0));
}
printf("\n");
} else
printf("System:\n Not booted with EFI\n\n");
@ -1284,6 +1338,122 @@ static int verb_list(int argc, char *argv[], void *userdata) {
return 0;
}
static int install_random_seed(const char *esp) {
_cleanup_(unlink_and_freep) char *tmp = NULL;
_cleanup_free_ void *buffer = NULL;
_cleanup_free_ char *path = NULL;
_cleanup_close_ int fd = -1;
size_t sz, token_size;
ssize_t n;
int r;
assert(esp);
path = path_join(esp, "/loader/random-seed");
if (!path)
return log_oom();
sz = random_pool_size();
buffer = malloc(sz);
if (!buffer)
return log_oom();
r = genuine_random_bytes(buffer, sz, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Faile to acquire random seed: %m");
r = tempfn_random(path, "bootctl", &tmp);
if (r < 0)
return log_oom();
fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|O_WRONLY|O_CLOEXEC, 0600);
if (fd < 0) {
tmp = mfree(tmp);
return log_error_errno(fd, "Failed to open random seed file for writing: %m");
}
n = write(fd, buffer, sz);
if (n < 0)
return log_error_errno(errno, "Failed to write random seed file: %m");
if ((size_t) n != sz)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing random seed file.");
if (rename(tmp, path) < 0)
return log_error_errno(r, "Failed to move random seed file into place: %m");
tmp = mfree(tmp);
log_info("Successfully written random seed file %s with %zu bytes.", path, sz);
if (!arg_touch_variables)
return 0;
if (!is_efi_boot()) {
log_notice("Not booted with EFI, skipping EFI variable setup.");
return 0;
}
r = getenv_bool("SYSTEMD_WRITE_SYSTEM_TOKEN");
if (r < 0) {
if (r != -ENXIO)
log_warning_errno(r, "Failed to parse $SYSTEMD_WRITE_SYSTEM_TOKEN, ignoring.");
if (detect_vm() > 0) {
/* Let's not write a system token if we detect we are running in a VM
* environment. Why? Our default security model for the random seed uses the system
* token as a mechanism to ensure we are not vulnerable to golden master sloppiness
* issues, i.e. that people initialize the random seed file, then copy the image to
* many systems and end up with the same random seed in each that is assumed to be
* valid but in reality is the same for all machines. By storing a system token in
* the EFI variable space we can make sure that even though the random seeds on disk
* are all the same they will be different on each system under the assumption that
* the EFI variable space is maintained separate from the random seed storage. That
* is generally the case on physical systems, as the ESP is stored on persistant
* storage, and the EFI variables in NVRAM. However in virtualized environments this
* is generally not true: the EFI variable set is typically stored along with the
* disk image itself. For example, using the OVMF EFI firmware the EFI variables are
* stored in a file in the ESP itself. */
log_notice("Not installing system token, since we are running in a virtualized environment.");
return 0;
}
} else if (r == 0) {
log_notice("Not writing system token, because $SYSTEMD_WRITE_SYSTEM_TOKEN is set to false.");
return 0;
}
r = efi_get_variable(EFI_VENDOR_LOADER, "LoaderSystemToken", NULL, NULL, &token_size);
if (r < 0) {
if (r != -ENOENT)
return log_error_errno(r, "Failed to test system token validity: %m");
} else {
if (token_size >= sz) {
/* Let's avoid writes if we can, and initialize this only once. */
log_debug("System token already written, not updating.");
return 0;
}
log_debug("Existing system token size (%zu) does not match our expectations (%zu), replacing.", token_size, sz);
}
r = genuine_random_bytes(buffer, sz, RANDOM_BLOCK);
if (r < 0)
return log_error_errno(r, "Failed to acquire random seed: %m");
/* Let's write this variable with an umask in effect, so that unprivileged users can't see the token
* and possibly get identification information or too much insight into the kernel's entropy pool
* state. */
RUN_WITH_UMASK(0077) {
r = efi_set_variable(EFI_VENDOR_LOADER, "LoaderSystemToken", buffer, sz);
if (r < 0)
return log_error_errno(r, "Failed to set LoaderSystemToken EFI variable: %m");
}
log_info("Successfully initialized system token in EFI variable with %zu bytes.", sz);
return 0;
}
static int sync_everything(void) {
int ret = 0, k;
@ -1329,7 +1499,11 @@ static int verb_install(int argc, char *argv[], void *userdata) {
/* Don't create any of these directories when we are just updating. When we update
* we'll drop-in our files (unless there are newer ones already), but we won't create
* the directories for them in the first place. */
r = create_esp_subdirs(arg_esp_path);
r = create_subdirs(arg_esp_path, esp_subdirs);
if (r < 0)
return r;
r = create_subdirs(arg_dollar_boot_path(), dollar_boot_subdirs);
if (r < 0)
return r;
}
@ -1343,7 +1517,11 @@ static int verb_install(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
r = install_entries_directories(arg_dollar_boot_path(), machine_id);
r = install_machine_id_directory(arg_dollar_boot_path(), machine_id);
if (r < 0)
return r;
r = install_random_seed(arg_esp_path);
if (r < 0)
return r;
}
@ -1361,7 +1539,7 @@ static int verb_install(int argc, char *argv[], void *userdata) {
}
static int verb_remove(int argc, char *argv[], void *userdata) {
sd_id128_t uuid = SD_ID128_NULL;
sd_id128_t uuid = SD_ID128_NULL, machine_id;
int r, q;
r = acquire_esp(false, NULL, NULL, NULL, &uuid);
@ -1372,31 +1550,97 @@ static int verb_remove(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
r = sd_id128_get_machine(&machine_id);
if (r < 0)
return log_error_errno(r, "Failed to get machine id: %m");
r = remove_binaries(arg_esp_path);
q = remove_loader_config(arg_esp_path);
q = remove_file(arg_esp_path, "/loader/loader.conf");
if (q < 0 && r >= 0)
r = q;
q = remove_entries_directory(arg_dollar_boot_path());
q = remove_file(arg_esp_path, "/loader/random-seed");
if (q < 0 && r >= 0)
r = q;
q = remove_esp_subdirs(arg_esp_path);
q = remove_subdirs(arg_esp_path, esp_subdirs);
if (q < 0 && r >= 0)
r = q;
(void) sync_everything();
q = remove_subdirs(arg_esp_path, dollar_boot_subdirs);
if (q < 0 && r >= 0)
r = q;
if (arg_touch_variables) {
q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true);
q = remove_machine_id_directory(arg_esp_path, machine_id);
if (q < 0 && r >= 0)
r = 1;
if (arg_xbootldr_path) {
/* Remove the latter two also in the XBOOTLDR partition if it exists */
q = remove_subdirs(arg_xbootldr_path, dollar_boot_subdirs);
if (q < 0 && r >= 0)
r = q;
q = remove_machine_id_directory(arg_xbootldr_path, machine_id);
if (q < 0 && r >= 0)
r = q;
}
(void) sync_everything();
if (!arg_touch_variables)
return r;
q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true);
if (q < 0 && r >= 0)
r = q;
q = remove_loader_variables();
if (q < 0 && r >= 0)
r = q;
return r;
}
static int verb_is_installed(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *p = NULL;
int r;
r = acquire_esp(false, NULL, NULL, NULL, NULL);
if (r < 0)
return r;
/* Tests whether systemd-boot is installed. It's not obvious what to use as check here: we could
* check EFI variables, we could check what binary /EFI/BOOT/BOOT*.EFI points to, or whether the
* loader entries directory exists. Here we opted to check whether /EFI/systemd/ is non-empty, which
* should be a suitable and very minimal check for a number of reasons:
*
* The check is architecture independent (i.e. we check if any systemd-boot loader is installed, not a
* specific one.)
*
* It doesn't assume we are the only boot loader (i.e doesn't check if we own the main
* /EFI/BOOT/BOOT*.EFI fallback binary.
*
* It specifically checks for systemd-boot, not for other boot loaders (which a check for
* /boot/loader/entries would do). */
p = path_join(arg_esp_path, "/EFI/systemd/");
if (!p)
return log_oom();
r = dir_is_empty(p);
if (r > 0 || r == -ENOENT) {
puts("no");
return EXIT_FAILURE;
}
if (r < 0)
return log_error_errno(r, "Failed to detect whether systemd-boot is installed: %m");
puts("yes");
return EXIT_SUCCESS;
}
static int verb_set_default(int argc, char *argv[], void *userdata) {
const char *name;
int r;
@ -1445,16 +1689,33 @@ static int verb_set_default(int argc, char *argv[], void *userdata) {
return 0;
}
static int verb_random_seed(int argc, char *argv[], void *userdata) {
int r;
r = acquire_esp(false, NULL, NULL, NULL, NULL);
if (r < 0)
return r;
r = install_random_seed(arg_esp_path);
if (r < 0)
return r;
(void) sync_everything();
return 0;
}
static int bootctl_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "install", VERB_ANY, 1, 0, verb_install },
{ "update", VERB_ANY, 1, 0, verb_install },
{ "remove", VERB_ANY, 1, 0, verb_remove },
{ "list", VERB_ANY, 1, 0, verb_list },
{ "set-default", 2, 2, 0, verb_set_default },
{ "set-oneshot", 2, 2, 0, verb_set_default },
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "install", VERB_ANY, 1, 0, verb_install },
{ "update", VERB_ANY, 1, 0, verb_install },
{ "remove", VERB_ANY, 1, 0, verb_remove },
{ "random-seed", VERB_ANY, 1, 0, verb_random_seed },
{ "is-installed", VERB_ANY, 1, 0, verb_is_installed },
{ "list", VERB_ANY, 1, 0, verb_list },
{ "set-default", 2, 2, 0, verb_set_default },
{ "set-oneshot", 2, 2, 0, verb_set_default },
{}
};

View File

@ -12,6 +12,7 @@
#include "loader-features.h"
#include "measure.h"
#include "pe.h"
#include "random-seed.h"
#include "shim.h"
#include "util.h"
@ -68,6 +69,7 @@ typedef struct {
BOOLEAN force_menu;
UINTN console_mode;
enum console_mode_change_type console_mode_change;
RandomSeedMode random_seed_mode;
} Config;
static VOID cursor_left(UINTN *cursor, UINTN *first) {
@ -396,6 +398,21 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
Print(L"editor: %s\n", yes_no(config->editor));
Print(L"auto-entries: %s\n", yes_no(config->auto_entries));
Print(L"auto-firmware: %s\n", yes_no(config->auto_firmware));
switch (config->random_seed_mode) {
case RANDOM_SEED_OFF:
Print(L"random-seed-mode: off\n");
break;
case RANDOM_SEED_WITH_SYSTEM_TOKEN:
Print(L"random-seed-node: with-system-token\n");
break;
case RANDOM_SEED_ALWAYS:
Print(L"random-seed-node: always\n");
break;
default:
;
}
Print(L"\n");
Print(L"config entry count: %d\n", config->entry_count);
@ -1038,7 +1055,9 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
if (EFI_ERROR(parse_boolean(value, &on)))
continue;
config->editor = on;
continue;
}
if (strcmpa((CHAR8 *)"auto-entries", key) == 0) {
@ -1046,7 +1065,9 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
if (EFI_ERROR(parse_boolean(value, &on)))
continue;
config->auto_entries = on;
continue;
}
if (strcmpa((CHAR8 *)"auto-firmware", key) == 0) {
@ -1054,7 +1075,9 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
if (EFI_ERROR(parse_boolean(value, &on)))
continue;
config->auto_firmware = on;
continue;
}
if (strcmpa((CHAR8 *)"console-mode", key) == 0) {
@ -1074,6 +1097,23 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
continue;
}
if (strcmpa((CHAR8*) "random-seed-mode", key) == 0) {
if (strcmpa((CHAR8*) "off", value) == 0)
config->random_seed_mode = RANDOM_SEED_OFF;
else if (strcmpa((CHAR8*) "with-system-token", value) == 0)
config->random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN;
else if (strcmpa((CHAR8*) "always", value) == 0)
config->random_seed_mode = RANDOM_SEED_ALWAYS;
else {
BOOLEAN on;
if (EFI_ERROR(parse_boolean(value, &on)))
continue;
config->random_seed_mode = on ? RANDOM_SEED_ALWAYS : RANDOM_SEED_OFF;
}
}
}
}
@ -1404,6 +1444,7 @@ static VOID config_load_defaults(Config *config, EFI_FILE *root_dir) {
.editor = TRUE,
.auto_entries = TRUE,
.auto_firmware = TRUE,
.random_seed_mode = RANDOM_SEED_WITH_SYSTEM_TOKEN,
};
err = file_read(root_dir, L"\\loader\\loader.conf", 0, 0, &content, NULL);
@ -2284,6 +2325,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
EFI_LOADER_FEATURE_ENTRY_ONESHOT |
EFI_LOADER_FEATURE_BOOT_COUNTING |
EFI_LOADER_FEATURE_XBOOTLDR |
EFI_LOADER_FEATURE_RANDOM_SEED |
0;
_cleanup_freepool_ CHAR16 *infostr = NULL, *typestr = NULL;
@ -2435,8 +2477,11 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
config_entry_bump_counters(entry, root_dir);
/* export the selected boot entry to the system */
efivar_set(L"LoaderEntrySelected", entry->id, FALSE);
/* Export the selected boot entry to the system */
(VOID) efivar_set(L"LoaderEntrySelected", entry->id, FALSE);
/* Optionally, read a random seed off the ESP and pass it to the OS */
(VOID) process_random_seed(root_dir, config.random_seed_mode);
uefi_call_wrapper(BS->SetWatchdogTimer, 4, 5 * 60, 0x10000, 0, NULL);
err = image_start(image, &config, entry);

View File

@ -11,3 +11,4 @@
#define EFI_LOADER_FEATURE_ENTRY_ONESHOT (UINT64_C(1) << 3)
#define EFI_LOADER_FEATURE_BOOT_COUNTING (UINT64_C(1) << 4)
#define EFI_LOADER_FEATURE_XBOOTLDR (UINT64_C(1) << 5)
#define EFI_LOADER_FEATURE_RANDOM_SEED (UINT64_C(1) << 6)

View File

@ -8,6 +8,8 @@ efi_headers = files('''
linux.h
measure.h
pe.h
random-seed.h
sha256.h
shim.h
splash.h
util.h
@ -24,8 +26,10 @@ common_sources = '''
systemd_boot_sources = '''
boot.c
console.c
shim.c
crc32.c
random-seed.c
sha256.c
shim.c
'''.split()
stub_sources = '''

340
src/boot/efi/random-seed.c Normal file
View File

@ -0,0 +1,340 @@
#include <efi.h>
#include <efilib.h>
#include "random-seed.h"
#include "sha256.h"
#include "util.h"
#include "shim.h"
#define RANDOM_MAX_SIZE_MIN (32U)
#define RANDOM_MAX_SIZE_MAX (32U*1024U)
static const EFI_GUID rng_protocol_guid = EFI_RNG_PROTOCOL_GUID;
/* SHA256 gives us 256/8=32 bytes */
#define HASH_VALUE_SIZE 32
static EFI_STATUS acquire_rng(UINTN size, VOID **ret) {
_cleanup_freepool_ VOID *data = NULL;
EFI_RNG_PROTOCOL *rng;
EFI_STATUS err;
/* Try to acquire the specified number of bytes from the UEFI RNG */
err = LibLocateProtocol((EFI_GUID*) &rng_protocol_guid, (VOID**) &rng);
if (EFI_ERROR(err)) {
Print(L"Failed to acquire RNG protocol: %r\n", err);
return err;
}
if (!rng) {
/* Print(L"RNG protocol not available.\n"); */
return EFI_UNSUPPORTED;
}
data = AllocatePool(size);
if (!data)
return log_oom();
err = uefi_call_wrapper(rng->GetRNG, 3, rng, NULL, size, data);
if (EFI_ERROR(err)) {
Print(L"Failed to acquire RNG data: %r\n", err);
return err;
}
*ret = TAKE_PTR(data);
return EFI_SUCCESS;
}
static VOID hash_once(
const VOID *old_seed,
const VOID *rng,
UINTN size,
const VOID *system_token,
UINTN system_token_size,
UINTN counter,
UINT8 ret[static HASH_VALUE_SIZE]) {
/* This hashes together:
*
* 1. The contents of the old seed file
* 2. Some random data acquired from the UEFI RNG (optional)
* 3. Some 'system token' the installer installed as EFI variable (optional)
* 4. A counter value
*
* And writes the result to the specified buffer.
*/
struct sha256_ctx hash;
sha256_init_ctx(&hash);
sha256_process_bytes(old_seed, size, &hash);
if (rng)
sha256_process_bytes(rng, size, &hash);
if (system_token_size > 0)
sha256_process_bytes(system_token, system_token_size, &hash);
sha256_process_bytes(&counter, sizeof(counter), &hash);
sha256_finish_ctx(&hash, ret);
}
static EFI_STATUS hash_many(
const VOID *old_seed,
const VOID *rng,
UINTN size,
const VOID *system_token,
UINTN system_token_size,
UINTN counter_start,
UINTN n,
VOID **ret) {
_cleanup_freepool_ VOID *output = NULL;
UINTN i;
/* Hashes the specified parameters in counter mode, generating n hash values, with the counter in the
* range counter_startcounter_start+n-1. */
output = AllocatePool(n * HASH_VALUE_SIZE);
if (!output)
return log_oom();
for (i = 0; i < n; i++)
hash_once(old_seed, rng, size,
system_token, system_token_size,
counter_start + i,
(UINT8*) output + (i * HASH_VALUE_SIZE));
*ret = TAKE_PTR(output);
return EFI_SUCCESS;
}
static EFI_STATUS mangle_random_seed(
const VOID *old_seed,
const VOID *rng,
UINTN size,
const VOID *system_token,
UINTN system_token_size,
VOID **ret_new_seed,
VOID **ret_for_kernel) {
_cleanup_freepool_ VOID *new_seed = NULL, *for_kernel = NULL;
EFI_STATUS err;
UINTN n;
/* This takes the old seed file contents, an (optional) random number acquired from the UEFI RNG, an
* (optional) system 'token' installed once by the OS installer in an EFI variable, and hashes them
* together in counter mode, generating a new seed (to replace the file on disk) and the seed for the
* kernel. To keep things simple, the new seed and kernel data have the same size as the old seed and
* RNG data. */
n = (size + HASH_VALUE_SIZE - 1) / HASH_VALUE_SIZE;
/* Begin hashing in counter mode at counter 0 for the new seed for the disk */
err = hash_many(old_seed, rng, size, system_token, system_token_size, 0, n, &new_seed);
if (EFI_ERROR(err))
return err;
/* Continue counting at 'n' for the seed for the kernel */
err = hash_many(old_seed, rng, size, system_token, system_token_size, n, n, &for_kernel);
if (EFI_ERROR(err))
return err;
*ret_new_seed = TAKE_PTR(new_seed);
*ret_for_kernel = TAKE_PTR(for_kernel);
return EFI_SUCCESS;
}
EFI_STATUS acquire_system_token(VOID **ret, UINTN *ret_size) {
_cleanup_freepool_ CHAR8 *data = NULL;
EFI_STATUS err;
UINTN size;
err = efivar_get_raw(&loader_guid, L"LoaderSystemToken", &data, &size);
if (EFI_ERROR(err)) {
if (err != EFI_NOT_FOUND)
Print(L"Failed to read LoaderSystemToken EFI variable: %r", err);
return err;
}
if (size <= 0) {
Print(L"System token too short, ignoring.");
return EFI_NOT_FOUND;
}
*ret = TAKE_PTR(data);
*ret_size = size;
return EFI_SUCCESS;
}
static VOID validate_sha256(void) {
#ifndef __OPTIMIZE__
/* Let's validate our SHA256 implementation. We stole it from glibc, and converted it to UEFI
* style. We better check whether it does the right stuff. We use the simpler test vectors from the
* SHA spec. Note that we strip this out in optimization builds. */
static const struct {
const char *string;
uint8_t hash[HASH_VALUE_SIZE];
} array[] = {
{ "abc",
{ 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad }},
{ "",
{ 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14,
0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 }},
{ "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
{ 0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8,
0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67,
0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1 }},
{ "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
{ 0xcf, 0x5b, 0x16, 0xa7, 0x78, 0xaf, 0x83, 0x80,
0x03, 0x6c, 0xe5, 0x9e, 0x7b, 0x04, 0x92, 0x37,
0x0b, 0x24, 0x9b, 0x11, 0xe8, 0xf0, 0x7a, 0x51,
0xaf, 0xac, 0x45, 0x03, 0x7a, 0xfe, 0xe9, 0xd1 }},
};
UINTN i;
for (i = 0; i < ELEMENTSOF(array); i++) {
struct sha256_ctx hash;
uint8_t result[HASH_VALUE_SIZE];
sha256_init_ctx(&hash);
sha256_process_bytes(array[i].string, strlena((const CHAR8*) array[i].string), &hash);
sha256_finish_ctx(&hash, result);
if (CompareMem(result, array[i].hash, HASH_VALUE_SIZE) != 0) {
Print(L"SHA256 failed validation.\n");
uefi_call_wrapper(BS->Stall, 1, 120 * 1000 * 1000);
return;
}
}
Print(L"SHA256 validated\n");
#endif
}
EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode) {
_cleanup_freepool_ VOID *seed = NULL, *new_seed = NULL, *rng = NULL, *for_kernel = NULL, *system_token = NULL;
_cleanup_(FileHandleClosep) EFI_FILE_HANDLE handle = NULL;
UINTN size, rsize, wsize, system_token_size = 0;
_cleanup_freepool_ EFI_FILE_INFO *info = NULL;
EFI_STATUS err;
validate_sha256();
if (mode == RANDOM_SEED_OFF) {
/* Print(L"Random seed handling turned off.\n"); */
return EFI_NOT_FOUND;
}
/* Let's better be safe than sorry, and for now disable this logic in SecureBoot mode, so that we
* don't credit a random seed that is not authenticated. */
if (secure_boot_enabled()) {
/* Print(L"Not loading random seed, because we are in SecureBoot mode.\n"); */
return EFI_NOT_FOUND;
}
/* Get some system specific seed that the installer might have placed in an EFI variable. We include
* it in our hash. This is protection against golden master image sloppiness, and it remains on the
* system, even when disk images are duplicated or swapped out. */
err = acquire_system_token(&system_token, &system_token_size);
if (mode != RANDOM_SEED_ALWAYS) {
/* if (err == EFI_NOT_FOUND) */
/* Print(L"Not loading random seed, because no system token is set.\n"); */
if (EFI_ERROR(err))
return err; /* in all other error cases we already logged */
}
err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &handle, L"\\loader\\random-seed", EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0ULL);
if (EFI_ERROR(err)) {
if (err != EFI_NOT_FOUND)
Print(L"Failed to open random seed file: %r\n", err);
/* else */
/* Print(L"Not loading random seed, because there is none.\n"); */
return err;
}
info = LibFileInfo(handle);
if (!info)
return log_oom();
size = info->FileSize;
if (size < RANDOM_MAX_SIZE_MIN) {
Print(L"Random seed file is too short?\n");
return EFI_INVALID_PARAMETER;
}
if (size > RANDOM_MAX_SIZE_MAX) {
Print(L"Random seed file is too large?\n");
return EFI_INVALID_PARAMETER;
}
seed = AllocatePool(size);
if (!seed)
return log_oom();
rsize = size;
err = uefi_call_wrapper(handle->Read, 3, handle, &rsize, seed);
if (EFI_ERROR(err)) {
Print(L"Failed to read random seed file: %r\n", err);
return err;
}
if (rsize != size) {
Print(L"Short read on random seed file\n");
return EFI_PROTOCOL_ERROR;
}
err = uefi_call_wrapper(handle->SetPosition, 2, handle, 0);
if (EFI_ERROR(err)) {
Print(L"Failed to seek to beginning of random seed file: %r\n", err);
return err;
}
/* Request some random data from the UEFI RNG. We don't need this to work safely, but it's a good
* idea to use it because it helps us for cases where users mistakenly include a random seed in
* golden master images that are replicated many times. */
(VOID) acquire_rng(size, &rng); /* It's fine if this fails */
/* Calculate new random seed for the disk and what to pass to the kernel */
err = mangle_random_seed(seed, rng, size, system_token, system_token_size, &new_seed, &for_kernel);
if (EFI_ERROR(err))
return err;
/* Update the random seed on disk before we use it */
wsize = size;
err = uefi_call_wrapper(handle->Write, 3, handle, &wsize, new_seed);
if (EFI_ERROR(err)) {
Print(L"Failed to write random seed file: %r\n", err);
return err;
}
if (wsize != size) {
Print(L"Short write on random seed file\n");
return EFI_PROTOCOL_ERROR;
}
err = uefi_call_wrapper(handle->Flush, 1, handle);
if (EFI_ERROR(err)) {
Print(L"Failed to flush random seed file: %r\n");
return err;
}
/* We are good to go */
err = efivar_set_raw(&loader_guid, L"LoaderRandomSeed", for_kernel, size, FALSE);
if (EFI_ERROR(err)) {
Print(L"Failed to write random seed to EFI variable: %r\n", err);
return err;
}
return EFI_SUCCESS;
}

View File

@ -0,0 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <efi.h>
typedef enum RandomSeedMode {
RANDOM_SEED_OFF,
RANDOM_SEED_WITH_SYSTEM_TOKEN,
RANDOM_SEED_ALWAYS,
_RANDOM_SEED_MODE_MAX,
_RANDOM_SEED_MODE_INVALID = -1,
} RandomSeedMode;
EFI_STATUS process_random_seed(EFI_FILE *root_dir, RandomSeedMode mode);

275
src/boot/efi/sha256.c Normal file
View File

@ -0,0 +1,275 @@
/* Stolen from glibc and converted to UEFI style. In glibc it comes with the following copyright blurb: */
/* Functions to compute SHA256 message digest of files or memory blocks.
according to the definition of SHA256 in FIPS 180-2.
Copyright (C) 2007-2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
/* Written by Ulrich Drepper <drepper@redhat.com>, 2007. */
#include "sha256.h"
#if __BYTE_ORDER == __LITTLE_ENDIAN
# define SWAP(n) \
(((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
# define SWAP64(n) \
(((n) << 56) \
| (((n) & 0xff00) << 40) \
| (((n) & 0xff0000) << 24) \
| (((n) & 0xff000000) << 8) \
| (((n) >> 8) & 0xff000000) \
| (((n) >> 24) & 0xff0000) \
| (((n) >> 40) & 0xff00) \
| ((n) >> 56))
#else
# define SWAP(n) (n)
# define SWAP64(n) (n)
#endif
/* This array contains the bytes used to pad the buffer to the next
64-byte boundary. (FIPS 180-2:5.1.1) */
static const UINT8 fillbuf[64] = {
0x80, 0 /* , 0, 0, ... */
};
/* Constants for SHA256 from FIPS 180-2:4.2.2. */
static const UINT32 K[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
static void sha256_process_block(const void *, UINTN, struct sha256_ctx *);
/* Initialize structure containing state of computation.
(FIPS 180-2:5.3.2) */
void sha256_init_ctx(struct sha256_ctx *ctx) {
ctx->H[0] = 0x6a09e667;
ctx->H[1] = 0xbb67ae85;
ctx->H[2] = 0x3c6ef372;
ctx->H[3] = 0xa54ff53a;
ctx->H[4] = 0x510e527f;
ctx->H[5] = 0x9b05688c;
ctx->H[6] = 0x1f83d9ab;
ctx->H[7] = 0x5be0cd19;
ctx->total64 = 0;
ctx->buflen = 0;
}
/* Process the remaining bytes in the internal buffer and the usual
prolog according to the standard and write the result to RESBUF.
IMPORTANT: On some systems it is required that RESBUF is correctly
aligned for a 32 bits value. */
void *sha256_finish_ctx(struct sha256_ctx *ctx, void *resbuf) {
/* Take yet unprocessed bytes into account. */
UINT32 bytes = ctx->buflen;
UINTN pad, i;
/* Now count remaining bytes. */
ctx->total64 += bytes;
pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
CopyMem (&ctx->buffer[bytes], fillbuf, pad);
/* Put the 64-bit file length in *bits* at the end of the buffer. */
ctx->buffer32[(bytes + pad + 4) / 4] = SWAP (ctx->total[TOTAL64_low] << 3);
ctx->buffer32[(bytes + pad) / 4] = SWAP ((ctx->total[TOTAL64_high] << 3)
| (ctx->total[TOTAL64_low] >> 29));
/* Process last bytes. */
sha256_process_block (ctx->buffer, bytes + pad + 8, ctx);
/* Put result from CTX in first 32 bytes following RESBUF. */
for (i = 0; i < 8; ++i)
((UINT32 *) resbuf)[i] = SWAP (ctx->H[i]);
return resbuf;
}
void sha256_process_bytes(const void *buffer, UINTN len, struct sha256_ctx *ctx) {
/* When we already have some bits in our internal buffer concatenate
both inputs first. */
if (ctx->buflen != 0) {
UINTN left_over = ctx->buflen;
UINTN add = 128 - left_over > len ? len : 128 - left_over;
CopyMem (&ctx->buffer[left_over], buffer, add);
ctx->buflen += add;
if (ctx->buflen > 64) {
sha256_process_block (ctx->buffer, ctx->buflen & ~63, ctx);
ctx->buflen &= 63;
/* The regions in the following copy operation cannot overlap. */
CopyMem (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
ctx->buflen);
}
buffer = (const char *) buffer + add;
len -= add;
}
/* Process available complete blocks. */
if (len >= 64) {
#if !_STRING_ARCH_unaligned
/* To check alignment gcc has an appropriate operator. Other
compilers don't. */
# if __GNUC__ >= 2
# define UNALIGNED_P(p) (((UINTN) p) % __alignof__ (UINT32) != 0)
# else
# define UNALIGNED_P(p) (((UINTN) p) % sizeof (UINT32) != 0)
# endif
if (UNALIGNED_P (buffer))
while (len > 64) {
CopyMem (ctx->buffer, buffer, 64);
sha256_process_block (ctx->buffer, 64, ctx);
buffer = (const char *) buffer + 64;
len -= 64;
}
else
#endif
{
sha256_process_block (buffer, len & ~63, ctx);
buffer = (const char *) buffer + (len & ~63);
len &= 63;
}
}
/* Move remaining bytes into internal buffer. */
if (len > 0) {
UINTN left_over = ctx->buflen;
CopyMem (&ctx->buffer[left_over], buffer, len);
left_over += len;
if (left_over >= 64) {
sha256_process_block (ctx->buffer, 64, ctx);
left_over -= 64;
CopyMem (ctx->buffer, &ctx->buffer[64], left_over);
}
ctx->buflen = left_over;
}
}
/* Process LEN bytes of BUFFER, accumulating context into CTX.
It is assumed that LEN % 64 == 0. */
static void sha256_process_block(const void *buffer, UINTN len, struct sha256_ctx *ctx) {
const UINT32 *words = buffer;
UINTN nwords = len / sizeof (UINT32);
UINT32 a = ctx->H[0];
UINT32 b = ctx->H[1];
UINT32 c = ctx->H[2];
UINT32 d = ctx->H[3];
UINT32 e = ctx->H[4];
UINT32 f = ctx->H[5];
UINT32 g = ctx->H[6];
UINT32 h = ctx->H[7];
/* First increment the byte count. FIPS 180-2 specifies the possible
length of the file up to 2^64 bits. Here we only compute the
number of bytes. */
ctx->total64 += len;
/* Process all bytes in the buffer with 64 bytes in each round of
the loop. */
while (nwords > 0) {
UINT32 W[64];
UINT32 a_save = a;
UINT32 b_save = b;
UINT32 c_save = c;
UINT32 d_save = d;
UINT32 e_save = e;
UINT32 f_save = f;
UINT32 g_save = g;
UINT32 h_save = h;
UINTN t;
/* Operators defined in FIPS 180-2:4.1.2. */
#define Ch(x, y, z) ((x & y) ^ (~x & z))
#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z))
#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22))
#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25))
#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3))
#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10))
/* It is unfortunate that C does not provide an operator for
cyclic rotation. Hope the C compiler is smart enough. */
#define CYCLIC(w, s) ((w >> s) | (w << (32 - s)))
/* Compute the message schedule according to FIPS 180-2:6.2.2 step 2. */
for (t = 0; t < 16; ++t) {
W[t] = SWAP (*words);
++words;
}
for (t = 16; t < 64; ++t)
W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16];
/* The actual computation according to FIPS 180-2:6.2.2 step 3. */
for (t = 0; t < 64; ++t) {
UINT32 T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t];
UINT32 T2 = S0 (a) + Maj (a, b, c);
h = g;
g = f;
f = e;
e = d + T1;
d = c;
c = b;
b = a;
a = T1 + T2;
}
/* Add the starting values of the context according to FIPS 180-2:6.2.2
step 4. */
a += a_save;
b += b_save;
c += c_save;
d += d_save;
e += e_save;
f += f_save;
g += g_save;
h += h_save;
/* Prepare for the next round. */
nwords -= 16;
}
/* Put checksum in context given as argument. */
ctx->H[0] = a;
ctx->H[1] = b;
ctx->H[2] = c;
ctx->H[3] = d;
ctx->H[4] = e;
ctx->H[5] = f;
ctx->H[6] = g;
ctx->H[7] = h;
}

28
src/boot/efi/sha256.h Normal file
View File

@ -0,0 +1,28 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include <efi.h>
#include <efilib.h>
struct sha256_ctx {
UINT32 H[8];
union {
UINT64 total64;
#define TOTAL64_low (1 - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
#define TOTAL64_high (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
UINT32 total[2];
};
UINT32 buflen;
union {
UINT8 buffer[128]; /* NB: always correctly aligned for UINT32. */
UINT32 buffer32[32];
UINT64 buffer64[16];
};
};
void sha256_init_ctx(struct sha256_ctx *ctx);
void *sha256_finish_ctx(struct sha256_ctx *ctx, VOID *resbuf);
void sha256_process_bytes(const void *buffer, UINTN len, struct sha256_ctx *ctx);

View File

@ -309,8 +309,8 @@ CHAR8 *strchra(CHAR8 *s, CHAR8 c) {
return NULL;
}
EFI_STATUS file_read(EFI_FILE_HANDLE dir, const CHAR16 *name, UINTN off, UINTN size, CHAR8 **content, UINTN *content_size) {
EFI_FILE_HANDLE handle;
EFI_STATUS file_read(EFI_FILE_HANDLE dir, const CHAR16 *name, UINTN off, UINTN size, CHAR8 **ret, UINTN *ret_size) {
_cleanup_(FileHandleClosep) EFI_FILE_HANDLE handle = NULL;
_cleanup_freepool_ CHAR8 *buf = NULL;
EFI_STATUS err;
@ -322,6 +322,9 @@ EFI_STATUS file_read(EFI_FILE_HANDLE dir, const CHAR16 *name, UINTN off, UINTN s
_cleanup_freepool_ EFI_FILE_INFO *info;
info = LibFileInfo(handle);
if (!info)
return EFI_OUT_OF_RESOURCES;
size = info->FileSize+1;
}
@ -332,15 +335,24 @@ EFI_STATUS file_read(EFI_FILE_HANDLE dir, const CHAR16 *name, UINTN off, UINTN s
}
buf = AllocatePool(size + 1);
if (!buf)
return EFI_OUT_OF_RESOURCES;
err = uefi_call_wrapper(handle->Read, 3, handle, &size, buf);
if (!EFI_ERROR(err)) {
buf[size] = '\0';
*content = buf;
buf = NULL;
if (content_size)
*content_size = size;
}
uefi_call_wrapper(handle->Close, 1, handle);
if (EFI_ERROR(err))
return err;
buf[size] = '\0';
*ret = TAKE_PTR(buf);
if (ret_size)
*ret_size = size;
return err;
}
EFI_STATUS log_oom(void) {
Print(L"Out of memory.");
(void) uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000);
return EFI_OUT_OF_RESOURCES;
}

View File

@ -66,3 +66,5 @@ const EFI_GUID loader_guid;
(ptr) = NULL; \
_ptr_; \
})
EFI_STATUS log_oom(void);

110
src/core/efi-random.c Normal file
View File

@ -0,0 +1,110 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include <fcntl.h>
#include <linux/random.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "alloc-util.h"
#include "chattr-util.h"
#include "efi-random.h"
#include "efivars.h"
#include "fd-util.h"
#include "fs-util.h"
#include "strv.h"
/* If a random seed was passed by the boot loader in the LoaderRandomSeed EFI variable, let's credit it to
* the kernel's random pool, but only once per boot. If this is run very early during initialization we can
* instantly boot up with a filled random pool.
*
* This makes no judgement on the entropy passed, it's the job of the boot loader to only pass us a seed that
* is suitably validated. */
static void lock_down_efi_variables(void) {
const char *p;
int r;
/* Paranoia: let's restrict access modes of these a bit, so that unprivileged users can't use them to
* identify the system or gain too much insight into what we might have credited to the entropy
* pool. */
FOREACH_STRING(p,
"/sys/firmware/efi/efivars/LoaderRandomSeed-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f",
"/sys/firmware/efi/efivars/LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f") {
r = chattr_path(p, 0, FS_IMMUTABLE_FL, NULL);
if (r == -ENOENT)
continue;
if (r < 0)
log_warning_errno(r, "Failed to drop FS_IMMUTABLE_FL from %s, ignoring: %m", p);
if (chmod(p, 0600) < 0)
log_warning_errno(errno, "Failed to reduce access mode of %s, ignoring: %m", p);
}
}
int efi_take_random_seed(void) {
_cleanup_free_ struct rand_pool_info *info = NULL;
_cleanup_free_ void *value = NULL;
_cleanup_close_ int random_fd = -1;
size_t size;
int r;
/* Paranoia comes first. */
lock_down_efi_variables();
if (access("/run/systemd/efi-random-seed-taken", F_OK) < 0) {
if (errno != ENOENT) {
log_warning_errno(errno, "Failed to determine whether we already used the random seed token, not using it.");
return 0;
}
/* ENOENT means we haven't used it yet. */
} else {
log_debug("EFI random seed already used, not using again.");
return 0;
}
r = efi_get_variable(EFI_VENDOR_LOADER, "LoaderRandomSeed", NULL, &value, &size);
if (r == -EOPNOTSUPP) {
log_debug_errno(r, "System lacks EFI support, not initializing random seed from EFI variable.");
return 0;
}
if (r == -ENOENT) {
log_debug_errno(r, "Boot loader did not pass LoaderRandomSeed EFI variable, not crediting any entropy.");
return 0;
}
if (r < 0)
return log_warning_errno(r, "Failed to read LoaderRandomSeed EFI variable, ignoring: %m");
if (size == 0)
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Random seed passed from boot loader has zero size? Ignoring.");
/* The kernel API only accepts "int" as entropy count (which is in bits), let's avoid any chance for
* confusion here. */
if (size > INT_MAX / 8)
size = INT_MAX / 8;
random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY);
if (random_fd < 0)
return log_warning_errno(errno, "Failed to open /dev/urandom for writing, ignoring: %m");
/* Before we use the seed, let's mark it as used, so that we never credit it twice. Also, it's a nice
* way to let users known that we successfully acquired entropy from the boot laoder. */
r = touch("/run/systemd/efi-random-seed-taken");
if (r < 0)
return log_warning_errno(r, "Unable to mark EFI random seed as used, not using it: %m");
info = malloc(offsetof(struct rand_pool_info, buf) + size);
if (!info)
return log_oom();
info->entropy_count = size * 8;
info->buf_size = size;
memcpy(info->buf, value, size);
if (ioctl(random_fd, RNDADDENTROPY, info) < 0)
return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m");
log_info("Successfully credited entropy passed from boot loader.");
return 1;
}

4
src/core/efi-random.h Normal file
View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int efi_take_random_seed(void);

View File

@ -32,9 +32,10 @@
#include "clock-util.h"
#include "conf-parser.h"
#include "cpu-set-util.h"
#include "dbus.h"
#include "dbus-manager.h"
#include "dbus.h"
#include "def.h"
#include "efi-random.h"
#include "emergency-action.h"
#include "env-util.h"
#include "exit-status.h"
@ -2511,6 +2512,9 @@ int main(int argc, char *argv[]) {
error_message = "Failed to mount API filesystems";
goto finish;
}
/* The efivarfs is now mounted, let's read the random seed off it */
(void) efi_take_random_seed();
}
/* Save the original RLIMIT_NOFILE/RLIMIT_MEMLOCK so that we can reset it later when

View File

@ -66,6 +66,8 @@ libcore_sources = '''
device.h
dynamic-user.c
dynamic-user.h
efi-random.c
efi-random.h
emergency-action.c
emergency-action.h
execute.c

View File

@ -2,8 +2,14 @@
#include <errno.h>
#include <fcntl.h>
#include <linux/random.h>
#include <string.h>
#include <sys/ioctl.h>
#if USE_SYS_RANDOM_H
# include <sys/random.h>
#endif
#include <sys/stat.h>
#include <sys/xattr.h>
#include <unistd.h>
#include "sd-id128.h"
@ -14,21 +20,94 @@
#include "io-util.h"
#include "log.h"
#include "main-func.h"
#include "missing.h"
#include "mkdir.h"
#include "parse-util.h"
#include "random-util.h"
#include "string-util.h"
#include "util.h"
#include "xattr-util.h"
#define POOL_SIZE_MIN 512
#define POOL_SIZE_MAX (10*1024*1024)
typedef enum CreditEntropy {
CREDIT_ENTROPY_NO_WAY,
CREDIT_ENTROPY_YES_PLEASE,
CREDIT_ENTROPY_YES_FORCED,
} CreditEntropy;
static CreditEntropy may_credit(int seed_fd) {
_cleanup_free_ char *creditable = NULL;
const char *e;
int r;
assert(seed_fd >= 0);
e = getenv("SYSTEMD_RANDOM_SEED_CREDIT");
if (!e) {
log_debug("$SYSTEMD_RANDOM_SEED_CREDIT is not set, not crediting entropy.");
return CREDIT_ENTROPY_NO_WAY;
}
if (streq(e, "force")) {
log_debug("$SYSTEMD_RANDOM_SEED_CREDIT is set to 'force', crediting entropy.");
return CREDIT_ENTROPY_YES_FORCED;
}
r = parse_boolean(e);
if (r <= 0) {
if (r < 0)
log_warning_errno(r, "Failed to parse $SYSTEMD_RANDOM_SEED_CREDIT, not crediting entropy: %m");
else
log_debug("Crediting entropy is turned off via $SYSTEMD_RANDOM_SEED_CREDIT, not crediting entropy.");
return CREDIT_ENTROPY_NO_WAY;
}
/* Determine if the file is marked as creditable */
r = fgetxattr_malloc(seed_fd, "user.random-seed-creditable", &creditable);
if (r < 0) {
if (IN_SET(r, -ENODATA, -ENOSYS, -EOPNOTSUPP))
log_debug_errno(r, "Seed file is not marked as creditable, not crediting.");
else
log_warning_errno(r, "Failed to read extended attribute, ignoring: %m");
return CREDIT_ENTROPY_NO_WAY;
}
r = parse_boolean(creditable);
if (r <= 0) {
if (r < 0)
log_warning_errno(r, "Failed to parse user.random-seed-creditable extended attribute, ignoring: %s", creditable);
else
log_debug("Seed file is marked as not creditable, not crediting.");
return CREDIT_ENTROPY_NO_WAY;
}
/* Don't credit the random seed if we are in first-boot mode, because we are supposed to start from
* scratch. This is a safety precaution for cases where we people ship "golden" images with empty
* /etc but populated /var that contains a random seed. */
if (access("/run/systemd/first-boot", F_OK) < 0) {
if (errno != ENOENT) {
log_warning_errno(errno, "Failed to check whether we are in first-boot mode, not crediting entropy: %m");
return CREDIT_ENTROPY_NO_WAY;
}
/* If ENOENT all is good, we are not in first-boot mode. */
} else {
log_debug("Not crediting entropy, since booted in first-boot mode.");
return CREDIT_ENTROPY_NO_WAY;
}
return CREDIT_ENTROPY_YES_PLEASE;
}
static int run(int argc, char *argv[]) {
_cleanup_close_ int seed_fd = -1, random_fd = -1;
bool read_seed_file, write_seed_file;
bool read_seed_file, write_seed_file, synchronous;
_cleanup_free_ void* buf = NULL;
size_t buf_size = 0;
size_t buf_size;
struct stat st;
ssize_t k;
FILE *f;
int r;
log_setup_service();
@ -39,18 +118,7 @@ static int run(int argc, char *argv[]) {
umask(0022);
/* Read pool size, if possible */
f = fopen("/proc/sys/kernel/random/poolsize", "re");
if (f) {
if (fscanf(f, "%zu", &buf_size) > 0)
/* poolsize is in bits on 2.6, but we want bytes */
buf_size /= 8;
fclose(f);
}
if (buf_size < POOL_SIZE_MIN)
buf_size = POOL_SIZE_MIN;
buf_size = random_pool_size();
r = mkdir_parents(RANDOM_SEED, 0755);
if (r < 0)
@ -60,11 +128,11 @@ static int run(int argc, char *argv[]) {
* new data, to make sure the next boot gets seeded differently. */
if (streq(argv[1], "load")) {
int open_rw_error;
seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600);
open_rw_error = -errno;
if (seed_fd < 0) {
int open_rw_error = -errno;
write_seed_file = false;
seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY);
@ -81,15 +149,11 @@ static int run(int argc, char *argv[]) {
write_seed_file = true;
random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600);
if (random_fd < 0) {
write_seed_file = false;
random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600);
if (random_fd < 0)
return log_error_errno(errno, "Failed to open /dev/urandom: %m");
}
if (random_fd < 0)
return log_error_errno(errno, "Failed to open /dev/urandom: %m");
read_seed_file = true;
synchronous = true; /* make this invocation a synchronous barrier for random pool initialization */
} else if (streq(argv[1], "save")) {
@ -103,7 +167,7 @@ static int run(int argc, char *argv[]) {
read_seed_file = false;
write_seed_file = true;
synchronous = false;
} else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown verb '%s'.", argv[1]);
@ -113,7 +177,7 @@ static int run(int argc, char *argv[]) {
/* If the seed file is larger than what we expect, then honour the existing size and save/restore as much as it says */
if ((uint64_t) st.st_size > buf_size)
buf_size = MIN(st.st_size, POOL_SIZE_MAX);
buf_size = MIN(st.st_size, RANDOM_POOL_SIZE_MAX);
buf = malloc(buf_size);
if (!buf)
@ -121,57 +185,131 @@ static int run(int argc, char *argv[]) {
if (read_seed_file) {
sd_id128_t mid;
int z;
/* First, let's write the machine ID into /dev/urandom, not crediting entropy. Why? As an
* extra protection against "golden images" that are put together sloppily, i.e. images which
* are duplicated on multiple systems but where the random seed file is not properly
* reset. Frequently the machine ID is properly reset on those systems however (simply
* because it's easier to notice, if it isn't due to address clashes and so on, while random
* seed equivalence is generally not noticed easily), hence let's simply write the machined
* ID into the random pool too. */
r = sd_id128_get_machine(&mid);
if (r < 0)
log_debug_errno(r, "Failed to get machine ID, ignoring: %m");
else {
r = loop_write(random_fd, &mid, sizeof(mid), false);
if (r < 0)
log_debug_errno(r, "Failed to write machine ID to /dev/urandom, ignoring: %m");
}
k = loop_read(seed_fd, buf, buf_size, false);
if (k < 0)
r = log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m");
else if (k == 0) {
r = 0;
log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m");
else if (k == 0)
log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding.");
} else {
else {
CreditEntropy lets_credit;
(void) lseek(seed_fd, 0, SEEK_SET);
r = loop_write(random_fd, buf, (size_t) k, false);
if (r < 0)
log_error_errno(r, "Failed to write seed to /dev/urandom: %m");
}
lets_credit = may_credit(seed_fd);
/* Let's also write the machine ID into the random seed. Why? As an extra protection against "golden
* images" that are put together sloppily, i.e. images which are duplicated on multiple systems but
* where the random seed file is not properly reset. Frequently the machine ID is properly reset on
* those systems however (simply because it's easier to notice, if it isn't due to address clashes and
* so on, while random seed equivalence is generally not noticed easily), hence let's simply write the
* machined ID into the random pool too. */
z = sd_id128_get_machine(&mid);
if (z < 0)
log_debug_errno(z, "Failed to get machine ID, ignoring: %m");
else {
z = loop_write(random_fd, &mid, sizeof(mid), false);
if (z < 0)
log_debug_errno(z, "Failed to write machine ID to /dev/urandom, ignoring: %m");
/* Before we credit or use the entropy, let's make sure to securely drop the
* creditable xattr from the file, so that we never credit the same random seed
* again. Note that further down we'll write a new seed again, and likely mark it as
* credible again, hence this is just paranoia to close the short time window between
* the time we upload the random seed into the kernel and download the new one from
* it. */
if (fremovexattr(seed_fd, "user.random-seed-creditable") < 0) {
if (!IN_SET(errno, ENODATA, ENOSYS, EOPNOTSUPP))
log_warning_errno(errno, "Failed to remove extended attribute, ignoring: %m");
/* Otherwise, there was no creditable flag set, which is OK. */
} else {
r = fsync_full(seed_fd);
if (r < 0) {
log_warning_errno(r, "Failed to synchronize seed to disk, not crediting entropy: %m");
if (lets_credit == CREDIT_ENTROPY_YES_PLEASE)
lets_credit = CREDIT_ENTROPY_NO_WAY;
}
}
if (IN_SET(lets_credit, CREDIT_ENTROPY_YES_PLEASE, CREDIT_ENTROPY_YES_FORCED)) {
_cleanup_free_ struct rand_pool_info *info = NULL;
info = malloc(offsetof(struct rand_pool_info, buf) + k);
if (!info)
return log_oom();
info->entropy_count = k * 8;
info->buf_size = k;
memcpy(info->buf, buf, k);
if (ioctl(random_fd, RNDADDENTROPY, info) < 0)
return log_warning_errno(errno, "Failed to credit entropy, ignoring: %m");
} else {
r = loop_write(random_fd, buf, (size_t) k, false);
if (r < 0)
log_error_errno(r, "Failed to write seed to /dev/urandom: %m");
}
}
}
if (write_seed_file) {
/* This is just a safety measure. Given that we are root and
* most likely created the file ourselves the mode and owner
* should be correct anyway. */
(void) fchmod_and_chown(seed_fd, 0600, 0, 0);
bool getrandom_worked = false;
k = loop_read(random_fd, buf, buf_size, false);
/* This is just a safety measure. Given that we are root and most likely created the file
* ourselves the mode and owner should be correct anyway. */
r = fchmod_and_chown(seed_fd, 0600, 0, 0);
if (r < 0)
return log_error_errno(r, "Failed to adjust seed file ownership and access mode.");
/* Let's make this whole job asynchronous, i.e. let's make ourselves a barrier for
* proper initialization of the random pool. */
k = getrandom(buf, buf_size, GRND_NONBLOCK);
if (k < 0 && errno == EAGAIN && synchronous) {
log_notice("Kernel entropy pool is not initialized yet, waiting until it is.");
k = getrandom(buf, buf_size, 0); /* retry synchronously */
}
if (k < 0)
return log_error_errno(k, "Failed to read new seed from /dev/urandom: %m");
if (k == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Got EOF while reading from /dev/urandom.");
log_debug_errno(errno, "Failed to read random data with getrandom(), falling back to /dev/urandom: %m");
else if ((size_t) k < buf_size)
log_debug("Short read from getrandom(), falling back to /dev/urandom: %m");
else
getrandom_worked = true;
if (!getrandom_worked) {
/* Retry with classic /dev/urandom */
k = loop_read(random_fd, buf, buf_size, false);
if (k < 0)
return log_error_errno(k, "Failed to read new seed from /dev/urandom: %m");
if (k == 0)
return log_error_errno(SYNTHETIC_ERRNO(EIO),
"Got EOF while reading from /dev/urandom.");
}
r = loop_write(seed_fd, buf, (size_t) k, false);
if (r < 0)
return log_error_errno(r, "Failed to write new random seed file: %m");
if (ftruncate(seed_fd, k) < 0)
return log_error_errno(r, "Failed to truncate random seed file: %m");
r = fsync_full(seed_fd);
if (r < 0)
return log_error_errno(r, "Failed to synchronize seed file: %m");
/* If we got this random seed data from getrandom() the data is suitable for crediting
* entropy later on. Let's keep that in mind by setting an extended attribute. on the file */
if (getrandom_worked)
if (fsetxattr(seed_fd, "user.random-seed-creditable", "1", 1, 0) < 0)
log_full_errno(IN_SET(errno, ENOSYS, EOPNOTSUPP) ? LOG_DEBUG : LOG_WARNING, errno,
"Failed to mark seed file as creditable, ignoring: %m");
}
return r;
return 0;
}
DEFINE_MAIN_FUNCTION(run);

View File

@ -207,25 +207,32 @@ char* efi_variable_path(sd_id128_t vendor, const char *name) {
int efi_get_variable(
sd_id128_t vendor,
const char *name,
uint32_t *attribute,
void **value,
size_t *size) {
uint32_t *ret_attribute,
void **ret_value,
size_t *ret_size) {
_cleanup_close_ int fd = -1;
_cleanup_free_ char *p = NULL;
_cleanup_free_ void *buf = NULL;
struct stat st;
uint32_t a;
ssize_t n;
struct stat st;
_cleanup_free_ void *buf = NULL;
assert(name);
assert(value);
assert(size);
p = efi_variable_path(vendor, name);
if (!p)
return -ENOMEM;
if (!ret_value && !ret_size && !ret_attribute) {
/* If caller is not interested in anything, just check if the variable exists and is readable
* to us. */
if (access(p, R_OK) < 0)
return -errno;
return 0;
}
fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0)
return -errno;
@ -237,31 +244,41 @@ int efi_get_variable(
if (st.st_size > 4*1024*1024 + 4)
return -E2BIG;
n = read(fd, &a, sizeof(a));
if (n < 0)
return -errno;
if (n != sizeof(a))
return -EIO;
if (ret_value || ret_attribute) {
n = read(fd, &a, sizeof(a));
if (n < 0)
return -errno;
if (n != sizeof(a))
return -EIO;
}
buf = malloc(st.st_size - 4 + 2);
if (!buf)
return -ENOMEM;
if (ret_value) {
buf = malloc(st.st_size - 4 + 2);
if (!buf)
return -ENOMEM;
n = read(fd, buf, (size_t) st.st_size - 4);
if (n < 0)
return -errno;
if (n != (ssize_t) st.st_size - 4)
return -EIO;
n = read(fd, buf, (size_t) st.st_size - 4);
if (n < 0)
return -errno;
if (n != st.st_size - 4)
return -EIO;
/* Always NUL terminate (2 bytes, to protect UTF-16) */
((char*) buf)[st.st_size - 4] = 0;
((char*) buf)[st.st_size - 4 + 1] = 0;
/* Always NUL terminate (2 bytes, to protect UTF-16) */
((char*) buf)[st.st_size - 4] = 0;
((char*) buf)[st.st_size - 4 + 1] = 0;
}
*value = TAKE_PTR(buf);
*size = (size_t) st.st_size - 4;
/* Note that efivarfs interestingly doesn't require ftruncate() to update an existing EFI variable
* with a smaller value. */
if (attribute)
*attribute = a;
if (ret_attribute)
*ret_attribute = a;
if (ret_value)
*ret_value = TAKE_PTR(buf);
if (ret_size)
*ret_size = (size_t) st.st_size - 4;
return 0;
}

View File

@ -135,6 +135,8 @@ in_units = [
'sysinit.target.wants/'],
['systemd-bless-boot.service', 'ENABLE_EFI HAVE_BLKID'],
['systemd-boot-check-no-failures.service', ''],
['systemd-boot-system-token.service', 'ENABLE_EFI',
'sysinit.target.wants/'],
['systemd-coredump@.service', 'ENABLE_COREDUMP'],
['systemd-pstore.service', 'ENABLE_PSTORE'],
['systemd-firstboot.service', 'ENABLE_FIRSTBOOT',

View File

@ -0,0 +1,34 @@
# SPDX-License-Identifier: LGPL-2.1+
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Store a System Token in an EFI Variable
Documentation=man:systemd-boot-system-token.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=local-fs.target systemd-random-seed.service
Before=shutdown.target
# Don't run this in a VM environment, because there EFI variables are not
# actually stored in NVRAM, independent of regular storage.
ConditionVirtualization=no
# Only run this if the boot loader can support random seed initialization.
ConditionPathExists=/sys/firmware/efi/efivars/LoaderFeatures-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
# Only run this if there is no system token defined yet, or …
ConditionPathExists=|!/sys/firmware/efi/efivars/LoaderSystemToken-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
# … if the boot loader didn't pass the OS a random seed (and thus probably was missing the random seed file)
ConditionPathExists=|!/sys/firmware/efi/efivars/LoaderRandomSeed-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=@bindir@/bootctl random-seed

View File

@ -22,4 +22,9 @@ Type=oneshot
RemainAfterExit=yes
ExecStart=@rootlibexecdir@/systemd-random-seed load
ExecStop=@rootlibexecdir@/systemd-random-seed save
TimeoutSec=30s
# This service waits until the kernel's entropy pool is initialized, and may be
# used as ordering barrier for service that require an initialized entropy
# pool. Since initialization can take a while on entropy-starved systems, let's
# increase the time-out substantially here.
TimeoutSec=10min