mirror of
https://github.com/systemd/systemd
synced 2024-10-15 12:34:37 +00:00
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:
commit
47685d9d4b
14
TODO
14
TODO
|
@ -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
|
||||
|
|
|
@ -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
418
docs/RANDOM_SEEDS.md
Normal 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.
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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', [], ''],
|
||||
|
|
76
man/systemd-boot-system-token.service.xml
Normal file
76
man/systemd-boot-system-token.service.xml
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
340
src/boot/efi/random-seed.c
Normal 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_start…counter_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;
|
||||
}
|
14
src/boot/efi/random-seed.h
Normal file
14
src/boot/efi/random-seed.h
Normal 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
275
src/boot/efi/sha256.c
Normal 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
28
src/boot/efi/sha256.h
Normal 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);
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
110
src/core/efi-random.c
Normal 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
4
src/core/efi-random.h
Normal file
|
@ -0,0 +1,4 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
int efi_take_random_seed(void);
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
34
units/systemd-boot-system-token.service.in
Normal file
34
units/systemd-boot-system-token.service.in
Normal 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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue