1
0
mirror of https://github.com/systemd/systemd synced 2025-09-21 12:54:44 +02:00

Compare commits

...

19 Commits

Author SHA1 Message Date
Yu Watanabe
0c25f8fd0b
sd-boot: allow configuration of log levels (#38701)
This allows for more liberal usage of logging functionality as messages
will no longer always show up on screen, regardless of urgency. The log
level to use can be configured through an SMBIOS type 11 string
(`io.systemd.boot.loglevel=`) or by using the `log-level` option in
loader.conf. Valid values are debug, info, notice, warning, err, crit,
alert, and emerg. By default, info will be used.
2025-09-18 12:06:48 +09:00
Yu Watanabe
6f1bc004e9
basic/efivars: read EFI variables using one read(), not two (#38864)
In https://github.com/systemd/systemd/issues/38842 it is reported that
we're again having trouble accessing EFI variables:
```
  [  292.212415] H (udev-worker)[253]: Reading EFI variable /sys/firmware/efi/efivars/LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f.
  ...
  [  344.397961] H (udev-worker)[253]: Detected slow EFI variable read access on LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f: 52.185510s
```

We don't know what causes the slowdown, but it seems reasonable to avoid
unnecessary read() calls. We would read the 4-byte attr first, and then
the actual value later. But our code always reads the value (and
discards the attr in all cases except one, when _writing_ the variable),
so let's optimize for the case where we read the value and read the
whole contents in one read().
2025-09-18 12:03:30 +09:00
Yu Watanabe
46869e32cd
resolve: two cleanups for varlink method (#38467) 2025-09-18 11:57:04 +09:00
Luca Boccassi
773a7d9a4e mount-util: support 'atime' family of mount options for fsmount() 2025-09-18 11:54:39 +09:00
Tobias Heider
f405165065 stub: fix file path handling for loaded kernel
- Actually pass the new memory file path to parent_loaded_image->FilePath
- Restore old parent_loaded_image if Linux returns
- Pass the same kernel_file_path in load_via_boot_services path
- s/Re-use/Patch in comment explaining what we are doing

Fixes #38566
2025-09-18 11:40:43 +09:00
Yu Watanabe
f871c20bad
systemd-repart: add encryption configs into repart.d/* (#38052)
As explained in https://github.com/systemd/systemd/issues/37892, it
would be nice to define per-partition PCRs/key file to use.

The global default config will be still defined as cmdline options, and
`TPM2PCRs=` and `KeyFile=` will be overriden by them.
2025-09-18 11:02:38 +09:00
Yu Watanabe
794f12c778 vmspawn: fix error handling
Follow-up for 9b5ba882bd9ae4e0ed270289eada03c2040fefe2.
2025-09-18 10:43:36 +09:00
Yu Watanabe
600c757b5f resolve/varlink: use sd_json_dispatch_const_string() at more places 2025-09-18 09:07:36 +09:00
Yu Watanabe
6c135f7ace resolve/varlink: replace json_dispatch_address() with json_dispatch_byte_array_iovec() 2025-09-18 09:06:48 +09:00
Felix Pehla
b3f3ce28f8 sd-boot: allow setting the log level through loader.conf
Allow configuring the log level used by sd-boot by setting
`log-level=<level>` in loader.conf. `info` is used by default.
2025-09-18 08:54:52 +09:00
Felix Pehla
0ce83b8a57 sd-boot: allow setting the log level through SMBIOS 11
Allow configuring the log level used by sd-boot by setting
`io.systemd.boot.loglevel=<level>` as SMBIOS type 11 string.
`info` is used if unset.
2025-09-18 08:54:52 +09:00
Felix Pehla
4f35d74998 sd-boot: allow setting the maximum log level 2025-09-18 08:54:52 +09:00
Felix Pehla
419e4dc450 sd-boot: efi-log: use log levels internally
Change log_internal() to receive a log level from which a text color is
derived, rather than the text color directly, and adjust various log_*
macros to use them internally.
2025-09-18 08:54:52 +09:00
Zbigniew Jędrzejewski-Szmek
4125e0c3db efivars: drop unused "optimization"
This was added in c242a082793df77a1dc0bce7f470660ab0a86fe5, and AFAICT, the
code was never exercised, not even in the tests. With this chunk gone, if
anyone ever calls the function without any output params, we'll do open + fstat
instead of access, which will work just fine too.
2025-09-17 09:45:47 +02:00
Zbigniew Jędrzejewski-Szmek
4b97478979 efivars: adjust location of variable attribute
I also thought about converting efi_set_variable() to use writev, but we don't
have loop_writev. I'm not sure if the loop around write here is important.
Coinceivably, it could make a difference it we were writing a long value.
The loop was introduced in b7749eb517ff5dd379cf61ee9fb50a0105ab2c0f, without
much comment unfortunately. So it doesn't seem worth the risk of changing this
to not use a loop, and writing loop_writev just for this also seems overkill.
2025-09-17 09:39:19 +02:00
Zbigniew Jędrzejewski-Szmek
9db9d6806e basic/efivars: read EFI variables using one read(), not two
In https://github.com/systemd/systemd/issues/38842 it is reported that we're again
having trouble accessing EFI variables:
  [  292.212415] H (udev-worker)[253]: Reading EFI variable /sys/firmware/efi/efivars/LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f.
  ...
  [  344.397961] H (udev-worker)[253]: Detected slow EFI variable read access on LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f: 52.185510s

We don't know what causes the slowdown, but it seems reasonable to avoid
unnecessary read() calls. We would read the 4-byte attr first, and then the
actual value later. But our code always reads the value (and discards the attr
in all cases except one, when _writing_ the variable), so let's optimize for
the case where we read the value and read the whole contents in one readv().
2025-09-17 09:27:54 +02:00
Emanuele Giuseppe Esposito
eb44fa4d19 repart: make --key-file also configurable in repart.d/*
Add repart.d KeyFile= option with the same syntax as --key-file.
This allows a per-partition key file encryption, and not rely on a global key
applicable to all partitions.

The global --key-file overrides KeyFile config. If none of them is
defined, rely on default.
2025-08-26 07:17:12 -04:00
Emanuele Giuseppe Esposito
49dcc89ddc repart: make --tpm2-pcrs also configurable in repart.d/*
Add repart.d TPM2PCRs= option with the same syntax as --tpm2-pcrs.
This allows a per-partition pcr binding, and not rely on a global config
applicable to all partitions.

The global --tpm2-pcrs overrides TPM2PCRs config. If none of them
is defined, rely on default.
2025-08-26 07:17:01 -04:00
Emanuele Giuseppe Esposito
d439799932 repart: use iovec structure for --key-file
Use the iovec structure for --key-file, instead of a char pointer and a size.
2025-08-26 07:07:53 -04:00
17 changed files with 498 additions and 236 deletions

View File

@ -480,6 +480,28 @@ sbvarsign --attr "${attr}" --key KEK.key --cert KEK.pem --output db.auth db db.e
<xi:include href="version-info.xml" xpointer="v258"/></listitem> <xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>log-level</term>
<listitem><para>Controls the log level used by
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
<para>Valid values are <literal>emerg</literal>, <literal>alert</literal>, <literal>crit</literal>,
<literal>err</literal>, <literal>warning</literal>, <literal>notice</literal>,
<literal>info</literal>, and <literal>debug</literal>.</para>
<para>If unspecified, <literal>info</literal> will be used, unless one has already been configured
via an SMBIOS Type 11 string, see
<citerefentry><refentrytitle>smbios-type-11</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
<para>Note that the configured level will only be used after <filename>loader.conf</filename> has
been parsed, so log messages generated before that point may be unaffected by this setting.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@ -869,6 +869,31 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem> <xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>TPM2PCRs=</varname></term>
<listitem><para>Configures the list of PCRs to use for LUKS2 volumes configured with
the <varname>Encrypt=tpm2</varname> setting in partition files.
This option take the same parameters as the similary named options to
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
and have the same effect on partitions where TPM2 enrollment is requested.
This option will be overridden by the global <varname>--tpm2-pcrs=</varname> option.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>KeyFile=</varname></term>
<listitem><para>Takes a file system path. This path must be absolute, otherwise the option is ignored.
Configures the encryption key to use when setting up LUKS2 volumes configured with the
<varname>Encrypt=key-file</varname> setting in partition files. Please refer to the documentation of
<varname>--key-file=</varname> for more details. This option will be overridden by the global
<varname>--key-file=</varname> option.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><varname>Compression=</varname></term> <term><varname>Compression=</varname></term>

View File

@ -80,10 +80,20 @@
<listitem><para>This allows inserting additional entries into the <command>systemd-boot</command> <listitem><para>This allows inserting additional entries into the <command>systemd-boot</command>
menu. For details see menu. For details see
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry></para> <citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem> <xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>io.systemd.boot.loglevel=</varname><replaceable>LEVEL</replaceable></term>
<listitem><para>This allows configuration of the log level, and is read by <command>systemd-boot</command>.
For details see
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@ -640,6 +640,16 @@ uki-url http://example.com/somedir/fooos.efi</programlisting>
<xi:include href="version-info.xml" xpointer="v258"/></listitem> <xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>io.systemd.boot.loglevel</varname></term>
<listitem><para>If set, the value of this string is used as log level. Valid values (from most to
least critical) are <literal>emerg</literal>, <literal>alert</literal>, <literal>crit</literal>,
<literal>err</literal>, <literal>warning</literal>, <literal>notice</literal>, <literal>info</literal>,
and <literal>debug</literal>.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
</variablelist> </variablelist>
</refsect1> </refsect1>

View File

@ -338,9 +338,9 @@
volumes configured with the <varname>Encrypt=key-file</varname> setting in partition files. Should volumes configured with the <varname>Encrypt=key-file</varname> setting in partition files. Should
refer to a regular file containing the key, or an <constant>AF_UNIX</constant> stream socket in the refer to a regular file containing the key, or an <constant>AF_UNIX</constant> stream socket in the
file system. In the latter case, a connection is made to it and the key read from it. If this switch file system. In the latter case, a connection is made to it and the key read from it. If this switch
is not specified, the empty key (i.e. zero length key) is used. This behaviour is useful for setting is not specified, and no <varname>KeyFile=</varname> is specified in the partition file, the empty
up encrypted partitions during early first boot that receive their user-supplied password only in a key (i.e. zero length key) is used. This behaviour is useful for setting up encrypted partitions during
later setup step.</para> early first boot that receive their user-supplied password only in a later setup step.</para>
<xi:include href="version-info.xml" xpointer="v247"/></listitem> <xi:include href="version-info.xml" xpointer="v247"/></listitem>
</varlistentry> </varlistentry>

View File

@ -3,6 +3,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <stdlib.h> #include <stdlib.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/uio.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
@ -31,104 +32,111 @@ int efi_get_variable(
void **ret_value, void **ret_value,
size_t *ret_size) { size_t *ret_size) {
_cleanup_close_ int fd = -EBADF;
_cleanup_free_ void *buf = NULL;
struct stat st;
usec_t begin = 0; /* Unnecessary initialization to appease gcc */ usec_t begin = 0; /* Unnecessary initialization to appease gcc */
uint32_t a;
ssize_t n;
assert(variable); assert(variable);
const char *p = strjoina("/sys/firmware/efi/efivars/", variable); const char *p = strjoina("/sys/firmware/efi/efivars/", variable);
if (!ret_value && !ret_size && !ret_attribute) {
/* If caller is not interested in anything, just check if the variable exists and is
* readable. */
if (access(p, R_OK) < 0)
return -errno;
return 0;
}
if (DEBUG_LOGGING) { if (DEBUG_LOGGING) {
log_debug("Reading EFI variable %s.", p); log_debug("Reading EFI variable %s.", p);
begin = now(CLOCK_MONOTONIC); begin = now(CLOCK_MONOTONIC);
} }
fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); _cleanup_close_ int fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (fd < 0) if (fd < 0)
return log_debug_errno(errno, "open(\"%s\") failed: %m", p); return log_debug_errno(errno, "open(\"%s\") failed: %m", p);
uint32_t attr;
_cleanup_free_ char *buf = NULL;
ssize_t n;
/* The kernel ratelimits reads from the efivarfs because EFI is inefficient, and we'll occasionally
* fail with EINTR here. A slowdown is better than a failure for us, so retry a few times and
* eventually fail with -EBUSY.
*
* See https://github.com/torvalds/linux/blob/master/fs/efivarfs/file.c#L75 and
* https://github.com/torvalds/linux/commit/bef3efbeb897b56867e271cdbc5f8adaacaeb9cd.
*
* The variable may also be overwritten between the stat and read. If we find out that the new
* contents are longer, try again.
*/
for (unsigned try = 0;; try++) {
struct stat st;
if (fstat(fd, &st) < 0) if (fstat(fd, &st) < 0)
return log_debug_errno(errno, "fstat(\"%s\") failed: %m", p); return log_debug_errno(errno, "fstat(\"%s\") failed: %m", p);
if (st.st_size == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
"EFI variable %s is uncommitted", p);
if (st.st_size < 4) if (st.st_size < 4)
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "EFI variable %s is shorter than 4 bytes, refusing.", p); return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "EFI variable %s is shorter than 4 bytes, refusing.", p);
if (st.st_size > 4*1024*1024 + 4) if (st.st_size > 4*1024*1024 + 4)
return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "EFI variable %s is ridiculously large, refusing.", p); return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "EFI variable %s is ridiculously large, refusing.", p);
if (ret_value || ret_attribute) { if (!ret_attribute && !ret_value) {
/* The kernel ratelimits reads from the efivarfs because EFI is inefficient, and we'll /* No need to read anything, return the reported size. */
* occasionally fail with EINTR here. A slowdown is better than a failure for us, so n = st.st_size;
* retry a few times and eventually fail with -EBUSY. break;
* }
* See https://github.com/torvalds/linux/blob/master/fs/efivarfs/file.c#L75
* and /* We want +1 for the read call, and +3 for the additional terminating bytes added below. */
* https://github.com/torvalds/linux/commit/bef3efbeb897b56867e271cdbc5f8adaacaeb9cd. char *t = realloc(buf, (size_t) st.st_size + MAX(1, 3));
*/ if (!t)
for (unsigned try = 0;; try++) { return -ENOMEM;
n = read(fd, &a, sizeof(a)); buf = t;
const struct iovec iov[] = {
{ &attr, sizeof(attr) },
{ buf, (size_t) st.st_size + 1 },
};
n = readv(fd, iov, 2);
assert(n <= st.st_size + 1);
if (n == st.st_size + 1)
/* We need to try again with a bigger buffer. */
continue;
if (n >= 0) if (n >= 0)
break; break;
log_debug_errno(errno, "Reading from \"%s\" failed: %m", p); log_debug_errno(errno, "Reading from \"%s\" failed: %m", p);
if (errno != EINTR) if (errno != EINTR)
return -errno; return -errno;
if (try >= EFI_N_RETRIES_TOTAL) if (try >= EFI_N_RETRIES_TOTAL)
return -EBUSY; return -EBUSY;
if (try >= EFI_N_RETRIES_NO_DELAY) if (try >= EFI_N_RETRIES_NO_DELAY)
(void) usleep_safe(EFI_RETRY_DELAY); (void) usleep_safe(EFI_RETRY_DELAY);
} }
/* Unfortunately kernel reports EOF if there's an inconsistency between efivarfs var list /* Unfortunately kernel reports EOF if there's an inconsistency between efivarfs var list and
* and what's actually stored in firmware, c.f. #34304. A zero size env var is not allowed in * what's actually stored in firmware, c.f. #34304. A zero size env var is not allowed in EFI
* efi and hence the variable doesn't really exist in the backing store as long as it is zero * and hence the variable doesn't really exist in the backing store as long as it is zero
* sized, and the kernel calls this "uncommitted". Hence we translate EOF back to ENOENT here, * sized, and the kernel calls this "uncommitted". Hence we translate EOF back to ENOENT
* as with kernel behavior before * here, as with kernel behavior before
* https://github.com/torvalds/linux/commit/3fab70c165795431f00ddf9be8b84ddd07bd1f8f * https://github.com/torvalds/linux/commit/3fab70c165795431f00ddf9be8b84ddd07bd1f8f.
* *
* If the kernel changes behaviour (to flush dentries on resume), we can drop * If the kernel changes behaviour (to flush dentries on resume), we can drop this at some
* this at some point in the future. But note that the commit is 11 * point in the future. But note that the commit is 11 years old at this point so we'll need
* years old at this point so we'll need to deal with the current behaviour for * to deal with the current behaviour for a long time.
* a long time.
*/ */
if (n == 0) if (n == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), return log_debug_errno(SYNTHETIC_ERRNO(ENOENT),
"EFI variable %s is uncommitted", p); "EFI variable %s is uncommitted", p);
if (n < 4)
if (n != sizeof(a))
return log_debug_errno(SYNTHETIC_ERRNO(EIO), return log_debug_errno(SYNTHETIC_ERRNO(EIO),
"Read %zi bytes from EFI variable %s, expected %zu.", n, p, sizeof(a)); "Read %zi bytes from EFI variable %s, expected >= 4", n, p);
}
if (ret_attribute)
*ret_attribute = attr;
if (ret_value) { if (ret_value) {
buf = malloc(st.st_size - 4 + 3); assert(buf);
if (!buf) /* Always NUL-terminate (3 bytes, to properly protect UTF-16, even if truncated in
return -ENOMEM; * the middle of a character) */
buf[n - 4] = 0;
n = read(fd, buf, (size_t) st.st_size - 4); buf[n - 4 + 1] = 0;
if (n < 0) buf[n - 4 + 2] = 0;
return log_debug_errno(errno, "Failed to read value of EFI variable %s: %m", p); *ret_value = TAKE_PTR(buf);
assert(n <= st.st_size - 4); }
/* Always NUL-terminate (3 bytes, to properly protect UTF-16, even if truncated in the middle
* of a character) */
((char*) buf)[n] = 0;
((char*) buf)[n + 1] = 0;
((char*) buf)[n + 2] = 0;
} else
/* Assume that the reported size is accurate */
n = st.st_size - 4;
if (DEBUG_LOGGING) { if (DEBUG_LOGGING) {
usec_t end = now(CLOCK_MONOTONIC); usec_t end = now(CLOCK_MONOTONIC);
@ -140,14 +148,8 @@ int efi_get_variable(
/* Note that efivarfs interestingly doesn't require ftruncate() to update an existing EFI variable /* Note that efivarfs interestingly doesn't require ftruncate() to update an existing EFI variable
* with a smaller value. */ * with a smaller value. */
if (ret_attribute)
*ret_attribute = a;
if (ret_value)
*ret_value = TAKE_PTR(buf);
if (ret_size) if (ret_size)
*ret_size = n; *ret_size = n - 4;
return 0; return 0;
} }
@ -208,10 +210,10 @@ static int efi_verify_variable(const char *variable, uint32_t attr, const void *
int efi_set_variable(const char *variable, const void *value, size_t size) { int efi_set_variable(const char *variable, const void *value, size_t size) {
static const uint32_t attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; static const uint32_t attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
struct var { _cleanup_free_ struct var {
uint32_t attr; uint32_t attr;
char buf[]; char buf[];
} _packed_ * _cleanup_free_ buf = NULL; } _packed_ *buf = NULL;
_cleanup_close_ int fd = -EBADF; _cleanup_close_ int fd = -EBADF;
bool saved_flags_valid = false; bool saved_flags_valid = false;
unsigned saved_flags; unsigned saved_flags;

View File

@ -361,6 +361,8 @@ static void print_status(Config *config, char16_t *loaded_image_path) {
if (config->console_mode_efivar != CONSOLE_MODE_KEEP) if (config->console_mode_efivar != CONSOLE_MODE_KEEP)
printf(" console-mode (EFI var): %" PRIi64 "\n", config->console_mode_efivar); printf(" console-mode (EFI var): %" PRIi64 "\n", config->console_mode_efivar);
printf(" log-level: %s\n", log_level_to_string(log_get_max_level()));
if (!ps_continue()) if (!ps_continue())
return; return;
@ -1111,6 +1113,9 @@ static void config_defaults_load_from_file(Config *config, char *content) {
} }
config->console_mode = u; config->console_mode = u;
} }
} else if (streq8(key, "log-level")) {
if (log_set_max_level_from_string(value) < 0)
log_error("Error parsing 'log-level' config option, ignoring: %s", value);
} }
} }
@ -2989,6 +2994,9 @@ static EFI_STATUS run(EFI_HANDLE image) {
uint64_t init_usec; uint64_t init_usec;
bool menu = false; bool menu = false;
/* set loglevel early to simplify debugging before loader.conf is loaded */
log_set_max_level_from_smbios();
init_usec = time_usec(); init_usec = time_usec();
err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image); err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image);

View File

@ -35,7 +35,7 @@ static EFI_STATUS load_one_driver(
if (loaded_image->ImageCodeType != EfiBootServicesCode && if (loaded_image->ImageCodeType != EfiBootServicesCode &&
loaded_image->ImageCodeType != EfiRuntimeServicesCode) loaded_image->ImageCodeType != EfiRuntimeServicesCode)
return log_error("Image %ls is not a driver, refusing.", fname); return log_error_status(EFI_INVALID_PARAMETER, "Image %ls is not a driver, refusing.", fname);
err = BS->StartImage(image, NULL, NULL); err = BS->StartImage(image, NULL, NULL);
if (err != EFI_SUCCESS) { if (err != EFI_SUCCESS) {

View File

@ -1,10 +1,78 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "efi-log.h" #include "efi-log.h"
#include "efi-string-table.h"
#include "proto/rng.h" #include "proto/rng.h"
#include "smbios.h"
#include "util.h" #include "util.h"
#include "vmm.h"
static unsigned log_count = 0; static unsigned log_count = 0;
static LogLevel log_max_level = LOG_INFO;
static const uint8_t log_level_color[_LOG_MAX] = {
[LOG_EMERG] = EFI_LIGHTRED,
[LOG_ALERT] = EFI_LIGHTRED,
[LOG_CRIT] = EFI_LIGHTRED,
[LOG_ERR] = EFI_LIGHTRED,
[LOG_WARNING] = EFI_YELLOW,
[LOG_NOTICE] = EFI_WHITE,
[LOG_INFO] = EFI_WHITE,
[LOG_DEBUG] = EFI_LIGHTGRAY,
};
static const char *const log_level_table[_LOG_MAX] = {
[LOG_EMERG] = "emerg",
[LOG_ALERT] = "alert",
[LOG_CRIT] = "crit",
[LOG_ERR] = "err",
[LOG_WARNING] = "warning",
[LOG_NOTICE] = "notice",
[LOG_INFO] = "info",
[LOG_DEBUG] = "debug",
};
DEFINE_STRING_TABLE_LOOKUP(log_level, LogLevel);
LogLevel log_get_max_level(void) {
return log_max_level;
}
int log_set_max_level(LogLevel level) {
assert(level >= 0 && level < _LOG_MAX);
int old = log_max_level;
log_max_level = level;
return old;
}
int log_set_max_level_from_string(const char *e) {
int r;
assert(e);
r = log_level_from_string(e);
if (r < 0)
return r;
log_set_max_level(r);
return 0;
}
void log_set_max_level_from_smbios(void) {
int r;
if (is_confidential_vm())
return; /* Don't consume SMBIOS in Confidential Computing contexts */
const char *level_str = smbios_find_oem_string("io.systemd.boot.loglevel=", /* after= */ NULL);
if (!level_str)
return;
r = log_set_max_level_from_string(level_str);
if (r < 0)
log_warning("Failed to parse log level '%s', ignoring.", level_str);
}
void freeze(void) { void freeze(void) {
for (;;) for (;;)
@ -31,14 +99,18 @@ void efi_assert(const char *expr, const char *file, unsigned line, const char *f
freeze(); freeze();
} }
EFI_STATUS log_internal(EFI_STATUS status, uint8_t text_color, const char *format, ...) { EFI_STATUS log_internal(EFI_STATUS status, LogLevel log_level, const char *format, ...) {
assert(format); assert(format);
assert(log_level >= 0 && log_level < _LOG_MAX);
if (log_level > log_max_level)
return status;
int32_t attr = ST->ConOut->Mode->Attribute; int32_t attr = ST->ConOut->Mode->Attribute;
if (ST->ConOut->Mode->CursorColumn > 0) if (ST->ConOut->Mode->CursorColumn > 0)
ST->ConOut->OutputString(ST->ConOut, (char16_t *) u"\r\n"); ST->ConOut->OutputString(ST->ConOut, (char16_t *) u"\r\n");
ST->ConOut->SetAttribute(ST->ConOut, EFI_TEXT_ATTR(text_color, EFI_BLACK)); ST->ConOut->SetAttribute(ST->ConOut, EFI_TEXT_ATTR(log_level_color[log_level], EFI_BLACK));
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);

View File

@ -21,17 +21,49 @@ __attribute__((no_stack_protector, noinline)) void __stack_chk_guard_init(void);
# define __stack_chk_guard_init() # define __stack_chk_guard_init()
#endif #endif
typedef enum LogLevel {
LOG_EMERG,
LOG_ALERT,
LOG_CRIT,
LOG_ERR,
LOG_WARNING,
LOG_NOTICE,
LOG_INFO,
LOG_DEBUG,
_LOG_MAX,
_LOG_INVALID = -1,
} LogLevel;
LogLevel log_level_from_string(const char *s) _pure_;
const char* log_level_to_string(LogLevel l) _const_;
LogLevel log_get_max_level(void) _pure_;
int log_set_max_level(LogLevel level);
int log_set_max_level_from_string(const char *e);
void log_set_max_level_from_smbios(void);
_noreturn_ void freeze(void); _noreturn_ void freeze(void);
void log_wait(void); void log_wait(void);
_gnu_printf_(3, 4) EFI_STATUS log_internal(EFI_STATUS status, uint8_t text_color, const char *format, ...); _gnu_printf_(3, 4) EFI_STATUS log_internal(EFI_STATUS status, LogLevel log_level, const char *format, ...);
#define log_full(status, text_color, format, ...) \
log_internal(status, text_color, "%s:%i@%s: " format, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define log_full(status, log_level, format, ...) \
#define log_debug(...) log_full(EFI_SUCCESS, EFI_LIGHTGRAY, __VA_ARGS__) log_internal(status, log_level, "%s:%i@%s: " format, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define log_info(...) log_full(EFI_SUCCESS, EFI_WHITE, __VA_ARGS__)
#define log_warning_status(status, ...) log_full(status, EFI_YELLOW, __VA_ARGS__) #define log_debug(...) log_full(EFI_SUCCESS, LOG_DEBUG, __VA_ARGS__)
#define log_error_status(status, ...) log_full(status, EFI_LIGHTRED, __VA_ARGS__) #define log_info(...) log_full(EFI_SUCCESS, LOG_INFO, __VA_ARGS__)
#define log_error(...) log_full(EFI_INVALID_PARAMETER, EFI_LIGHTRED, __VA_ARGS__) #define log_notice(...) log_full(EFI_SUCCESS, LOG_NOTICE, __VA_ARGS__)
#define log_oom() log_full(EFI_OUT_OF_RESOURCES, EFI_LIGHTRED, "Out of memory.") #define log_warning(...) log_full(EFI_SUCCESS, LOG_WARNING, __VA_ARGS__)
#define log_error(...) log_full(EFI_SUCCESS, LOG_ERR, __VA_ARGS__)
#define log_emergency(...) log_full(EFI_SUCCESS, LOG_EMERG, __VA_ARGS__)
#define log_debug_status(status, ...) log_full(status, LOG_DEBUG, __VA_ARGS__)
#define log_info_status(status, ...) log_full(status, LOG_INFO, __VA_ARGS__)
#define log_notice_status(status, ...) log_full(status, LOG_NOTICE, __VA_ARGS__)
#define log_warning_status(status, ...) log_full(status, LOG_WARNING, __VA_ARGS__)
#define log_error_status(status, ...) log_full(status, LOG_ERR, __VA_ARGS__)
#define log_emergency_status(status, ...) log_full(status, LOG_EMERG, __VA_ARGS__)
#define log_oom() log_full(EFI_OUT_OF_RESOURCES, LOG_ERR, "Out of memory.")
/* Debugging helper — please keep this around, even if not used */ /* Debugging helper — please keep this around, even if not used */
#define log_hexdump(prefix, data, size) \ #define log_hexdump(prefix, data, size) \

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once #pragma once
#include "efi-string.h"
#include "macro-fundamental.h" #include "macro-fundamental.h"
#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ #define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \
@ -9,5 +10,23 @@
return name##_table[i]; \ return name##_table[i]; \
} }
#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name, type, scope) \
scope type name##_from_string(const char *s) { \
if (!s) \
return (type) -1; \
for (size_t i = 0; i < ELEMENTSOF(name##_table); ++i) \
if (streq8(name##_table[i], s)) \
return (type) i; \
return (type) -1; \
}
#define _DEFINE_STRING_TABLE_LOOKUP(name, type, scope) \
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name, type, scope) \
_DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name, type, scope)
#define DEFINE_STRING_TABLE_LOOKUP(name, type) _DEFINE_STRING_TABLE_LOOKUP(name, type,)
#define DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,) #define DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,)
#define DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name, type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name, type,)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name, type) _DEFINE_STRING_TABLE_LOOKUP(name, type, static)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static)
#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name, type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name, type, static)

View File

@ -20,9 +20,6 @@
#include "shim.h" #include "shim.h"
#include "util.h" #include "util.h"
#define STUB_PAYLOAD_GUID \
{ 0x55c5d1f8, 0x04cd, 0x46b5, { 0x8a, 0x20, 0xe5, 0x6c, 0xbb, 0x30, 0x52, 0xd0 } }
typedef struct { typedef struct {
MEMMAP_DEVICE_PATH memmap_path; MEMMAP_DEVICE_PATH memmap_path;
EFI_DEVICE_PATH end_path; EFI_DEVICE_PATH end_path;
@ -56,22 +53,12 @@ static EFI_STATUS load_via_boot_services(
uint32_t compat_entry_point, uint32_t compat_entry_point,
const char16_t *cmdline, const char16_t *cmdline,
const struct iovec *kernel, const struct iovec *kernel,
const struct iovec *initrd) { const struct iovec *initrd,
KERNEL_FILE_PATH *kernel_file_path) {
_cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL; _cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL;
EFI_LOADED_IMAGE_PROTOCOL* loaded_image = NULL; EFI_LOADED_IMAGE_PROTOCOL* loaded_image = NULL;
EFI_STATUS err; EFI_STATUS err;
VENDOR_DEVICE_PATH device_node = {
.Header = {
.Type = MEDIA_DEVICE_PATH,
.SubType = MEDIA_VENDOR_DP,
.Length = sizeof(device_node),
},
.Guid = STUB_PAYLOAD_GUID,
};
_cleanup_free_ EFI_DEVICE_PATH* file_path = device_path_replace_node(parent_loaded_image->FilePath, NULL, &device_node.Header);
/* When running with shim < v16 and booting a UKI directly from it, without a second stage loader, /* When running with shim < v16 and booting a UKI directly from it, without a second stage loader,
* the shim verify protocol needs to be called or it will raise a security violation when starting * the shim verify protocol needs to be called or it will raise a security violation when starting
* the image (e.g.: Fedora Cloud Base UKI). TODO: drop once support for shim < v16 is not needed. */ * the image (e.g.: Fedora Cloud Base UKI). TODO: drop once support for shim < v16 is not needed. */
@ -81,13 +68,13 @@ static EFI_STATUS load_via_boot_services(
&(ValidationContext) { &(ValidationContext) {
.addr = kernel->iov_base, .addr = kernel->iov_base,
.len = kernel->iov_len, .len = kernel->iov_len,
.device_path = file_path, .device_path = &kernel_file_path->memmap_path.Header,
}); });
err = BS->LoadImage(/* BootPolicy= */false, err = BS->LoadImage(/* BootPolicy= */false,
parent, parent,
file_path, &kernel_file_path->memmap_path.Header,
kernel->iov_base, kernel->iov_base,
kernel->iov_len, kernel->iov_len,
&kernel_image); &kernel_image);
@ -204,17 +191,27 @@ EFI_STATUS linux_exec(
if (err != EFI_SUCCESS) if (err != EFI_SUCCESS)
return log_error_status(err, "Bad kernel image: %m"); return log_error_status(err, "Bad kernel image: %m");
/* Re-use the parent_image(_handle) and parent_loaded_image for the kernel image we are about to execute.
* We have to do this, because if kernel stub code passes its own handle to certain firmware functions,
* the firmware could cast EFI_LOADED_IMAGE_PROTOCOL * to a larger struct to access its own private data,
* and if we allocated a smaller struct, that could cause problems.
* This is modeled exactly after GRUB behaviour, which has proven to be functional. */
EFI_LOADED_IMAGE_PROTOCOL *parent_loaded_image; EFI_LOADED_IMAGE_PROTOCOL *parent_loaded_image;
err = BS->HandleProtocol( err = BS->HandleProtocol(
parent_image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &parent_loaded_image); parent_image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &parent_loaded_image);
if (err != EFI_SUCCESS) if (err != EFI_SUCCESS)
return log_error_status(err, "Cannot get parent loaded image: %m"); return log_error_status(err, "Cannot get parent loaded image: %m");
_cleanup_free_ KERNEL_FILE_PATH *kernel_file_path = xnew(KERNEL_FILE_PATH, 1);
*kernel_file_path = (KERNEL_FILE_PATH) {
.memmap_path = {
.Header = {
.Type = HARDWARE_DEVICE_PATH,
.SubType = HW_MEMMAP_DP,
.Length = sizeof(MEMMAP_DEVICE_PATH),
},
.MemoryType = EfiLoaderData,
.StartingAddress = POINTER_TO_PHYSICAL_ADDRESS(kernel->iov_base),
.EndingAddress = POINTER_TO_PHYSICAL_ADDRESS(kernel->iov_base) + kernel->iov_len,
},
.end_path = DEVICE_PATH_END_NODE,
};
/* If shim provides LoadImage, it comes from the new SHIM_IMAGE_LOADER interface added in shim 16, /* If shim provides LoadImage, it comes from the new SHIM_IMAGE_LOADER interface added in shim 16,
* and implements the following: * and implements the following:
* - shim hashes PE sections of PE binaries it authenticates and stores the hashes in a global * - shim hashes PE sections of PE binaries it authenticates and stores the hashes in a global
@ -241,7 +238,8 @@ EFI_STATUS linux_exec(
compat_entry_point, compat_entry_point,
cmdline, cmdline,
kernel, kernel,
initrd); initrd,
kernel_file_path);
err = pe_kernel_check_no_relocation(kernel->iov_base); err = pe_kernel_check_no_relocation(kernel->iov_base);
if (err != EFI_SUCCESS) if (err != EFI_SUCCESS)
@ -300,26 +298,13 @@ EFI_STATUS linux_exec(
} }
} }
_cleanup_free_ KERNEL_FILE_PATH *kernel_file_path = xnew(KERNEL_FILE_PATH, 1); /* Patch the parent_image(_handle) and parent_loaded_image for the kernel image we are about to execute.
* We have to do this, because if kernel stub code passes its own handle to certain firmware functions,
*kernel_file_path = (KERNEL_FILE_PATH) { * the firmware could cast EFI_LOADED_IMAGE_PROTOCOL * to a larger struct to access its own private data,
.memmap_path = { * and if we allocated a smaller struct, that could cause problems.
.Header = { * This is modeled exactly after GRUB behaviour, which has proven to be functional. */
.Type = HARDWARE_DEVICE_PATH, EFI_LOADED_IMAGE_PROTOCOL original_parent_loaded_image = *parent_loaded_image;
.SubType = HW_MEMMAP_DP, parent_loaded_image->FilePath = &kernel_file_path->memmap_path.Header;
.Length = sizeof(MEMMAP_DEVICE_PATH),
},
.MemoryType = EfiLoaderData,
.StartingAddress = POINTER_TO_PHYSICAL_ADDRESS(kernel->iov_base),
.EndingAddress = POINTER_TO_PHYSICAL_ADDRESS(kernel->iov_base) + kernel->iov_len,
},
.end_path = {
.Type = END_DEVICE_PATH_TYPE,
.SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
.Length = sizeof(EFI_DEVICE_PATH),
},
};
parent_loaded_image->ImageBase = loaded_kernel; parent_loaded_image->ImageBase = loaded_kernel;
parent_loaded_image->ImageSize = kernel_size_in_memory; parent_loaded_image->ImageSize = kernel_size_in_memory;
@ -346,6 +331,9 @@ EFI_STATUS linux_exec(
err = compat_entry(parent_image, ST); err = compat_entry(parent_image, ST);
} }
/* Restore */
*parent_loaded_image = original_parent_loaded_image;
/* On failure we'll free the buffers. EDK2 requires the memory buffers to be writable and /* On failure we'll free the buffers. EDK2 requires the memory buffers to be writable and
* non-executable, as in some configurations it will overwrite them with a fixed pattern, so if the * non-executable, as in some configurations it will overwrite them with a fixed pattern, so if the
* attributes are not restored FreePages() will crash. */ * attributes are not restored FreePages() will crash. */

View File

@ -211,10 +211,10 @@ EFI_STATUS process_random_seed(EFI_FILE *root_dir) {
size = info->FileSize; size = info->FileSize;
if (size < RANDOM_MAX_SIZE_MIN) if (size < RANDOM_MAX_SIZE_MIN)
return log_error("Random seed file is too short."); return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too short.");
if (size > RANDOM_MAX_SIZE_MAX) if (size > RANDOM_MAX_SIZE_MAX)
return log_error("Random seed file is too large."); return log_error_status(EFI_INVALID_PARAMETER, "Random seed file is too large.");
seed = xmalloc(size); seed = xmalloc(size);
rsize = size; rsize = size;

View File

@ -169,8 +169,7 @@ static bool arg_size_auto = false;
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0; static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true; static bool arg_legend = true;
static void *arg_key = NULL; static struct iovec arg_key = {};
static size_t arg_key_size = 0;
static char *arg_private_key = NULL; static char *arg_private_key = NULL;
static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE;
static char *arg_private_key_source = NULL; static char *arg_private_key_source = NULL;
@ -208,7 +207,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep); STATIC_DESTRUCTOR_REGISTER(arg_key, iovec_done_erase);
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep);
STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep);
@ -413,6 +412,9 @@ typedef struct Partition {
OrderedHashmap *subvolumes; OrderedHashmap *subvolumes;
char *default_subvolume; char *default_subvolume;
EncryptMode encrypt; EncryptMode encrypt;
struct iovec key;
Tpm2PCRValue *tpm2_hash_pcr_values;
size_t tpm2_n_hash_pcr_values;
VerityMode verity; VerityMode verity;
char *verity_match_key; char *verity_match_key;
MinimizeMode minimize; MinimizeMode minimize;
@ -676,10 +678,13 @@ static Partition* partition_free(Partition *p) {
strv_free(p->make_symlinks); strv_free(p->make_symlinks);
ordered_hashmap_free(p->subvolumes); ordered_hashmap_free(p->subvolumes);
free(p->default_subvolume); free(p->default_subvolume);
free(p->tpm2_hash_pcr_values);
free(p->verity_match_key); free(p->verity_match_key);
free(p->compression); free(p->compression);
free(p->compression_level); free(p->compression_level);
iovec_done_erase(&p->key);
copy_files_free_many(p->copy_files, p->n_copy_files); copy_files_free_many(p->copy_files, p->n_copy_files);
iovec_done(&p->roothash); iovec_done(&p->roothash);
@ -717,10 +722,13 @@ static void partition_foreignize(Partition *p) {
p->make_symlinks = strv_free(p->make_symlinks); p->make_symlinks = strv_free(p->make_symlinks);
p->subvolumes = ordered_hashmap_free(p->subvolumes); p->subvolumes = ordered_hashmap_free(p->subvolumes);
p->default_subvolume = mfree(p->default_subvolume); p->default_subvolume = mfree(p->default_subvolume);
p->tpm2_hash_pcr_values = mfree(p->tpm2_hash_pcr_values);
p->verity_match_key = mfree(p->verity_match_key); p->verity_match_key = mfree(p->verity_match_key);
p->compression = mfree(p->compression); p->compression = mfree(p->compression);
p->compression_level = mfree(p->compression_level); p->compression_level = mfree(p->compression_level);
iovec_done_erase(&p->key);
copy_files_free_many(p->copy_files, p->n_copy_files); copy_files_free_many(p->copy_files, p->n_copy_files);
p->copy_files = NULL; p->copy_files = NULL;
p->n_copy_files = 0; p->n_copy_files = 0;
@ -2467,6 +2475,78 @@ static int config_parse_encrypted_volume(
return 0; return 0;
} }
static int config_parse_tpm2_pcrs(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Partition *partition = ASSERT_PTR(data);
assert(rvalue);
if (isempty(rvalue)) {
/* Clear existing PCR values if empty */
partition->tpm2_hash_pcr_values = mfree(partition->tpm2_hash_pcr_values);
partition->tpm2_n_hash_pcr_values = 0;
return 0;
}
return tpm2_parse_pcr_argument_append(rvalue, &partition->tpm2_hash_pcr_values,
&partition->tpm2_n_hash_pcr_values);
}
static int parse_key_file(const char *filename, struct iovec *key) {
_cleanup_(erase_and_freep) char *k = NULL;
size_t n = 0;
int r;
r = read_full_file_full(
AT_FDCWD, filename,
/* offset= */ UINT64_MAX,
/* size= */ SIZE_MAX,
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
/* bind_name= */ NULL,
&k, &n);
if (r < 0)
return log_error_errno(r, "Failed to read key file '%s': %m", filename);
iovec_done_erase(key);
*key = IOVEC_MAKE(TAKE_PTR(k), n);
return 0;
}
static int config_parse_key_file(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
Partition *partition = ASSERT_PTR(userdata);
assert(rvalue);
if (isempty(rvalue)) {
iovec_done_erase(&partition->key);
return 0;
}
return parse_key_file(rvalue, &partition->key);
}
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF);
static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF);
@ -2572,6 +2652,8 @@ static int partition_read_definition(Partition *p, const char *path, const char
{ "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size }, { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size },
{ "Partition", "MountPoint", config_parse_mountpoint, 0, p }, { "Partition", "MountPoint", config_parse_mountpoint, 0, p },
{ "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p }, { "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p },
{ "Partition", "TPM2PCRs", config_parse_tpm2_pcrs, 0, p },
{ "Partition", "KeyFile", config_parse_key_file, 0, p },
{ "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression }, { "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression },
{ "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level }, { "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level },
{ "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name }, { "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name },
@ -4793,18 +4875,21 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); return log_error_errno(r, "Failed to LUKS2 format future partition: %m");
if (IN_SET(p->encrypt, ENCRYPT_KEY_FILE, ENCRYPT_KEY_FILE_TPM2)) { if (IN_SET(p->encrypt, ENCRYPT_KEY_FILE, ENCRYPT_KEY_FILE_TPM2)) {
/* Use partition-specific key if available, otherwise fall back to global key */
struct iovec *iovec_key = arg_key.iov_base ? &arg_key : &p->key;
r = sym_crypt_keyslot_add_by_volume_key( r = sym_crypt_keyslot_add_by_volume_key(
cd, cd,
CRYPT_ANY_SLOT, CRYPT_ANY_SLOT,
NULL, NULL,
VOLUME_KEY_SIZE, VOLUME_KEY_SIZE,
strempty(arg_key), strempty(iovec_key->iov_base),
arg_key_size); iovec_key->iov_len);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to add LUKS2 key: %m"); return log_error_errno(r, "Failed to add LUKS2 key: %m");
passphrase = strempty(arg_key); passphrase = strempty(iovec_key->iov_base);
passphrase_size = arg_key_size; passphrase_size = iovec_key->iov_len;
} }
if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) {
@ -4815,8 +4900,10 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
ssize_t base64_encoded_size; ssize_t base64_encoded_size;
int keyslot; int keyslot;
TPM2Flags flags = 0; TPM2Flags flags = 0;
Tpm2PCRValue *pcr_values = arg_tpm2_n_hash_pcr_values > 0 ? arg_tpm2_hash_pcr_values : p->tpm2_hash_pcr_values;
size_t n_pcr_values = arg_tpm2_n_hash_pcr_values > 0 ? arg_tpm2_n_hash_pcr_values : p->tpm2_n_hash_pcr_values;
if (arg_tpm2_n_hash_pcr_values == 0 && if (n_pcr_values == 0 &&
arg_tpm2_public_key_pcr_mask == 0 && arg_tpm2_public_key_pcr_mask == 0 &&
!arg_tpm2_pcrlock) !arg_tpm2_pcrlock)
log_notice("Notice: encrypting future partition %" PRIu64 ", locking against TPM2 with an empty policy, i.e. without any state or access restrictions.\n" log_notice("Notice: encrypting future partition %" PRIu64 ", locking against TPM2 with an empty policy, i.e. without any state or access restrictions.\n"
@ -4856,7 +4943,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
if (r < 0) if (r < 0)
return r; return r;
if (!tpm2_pcr_values_has_all_values(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values)) if (!tpm2_pcr_values_has_all_values(pcr_values, n_pcr_values))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Must provide all PCR values when using TPM2 device key."); "Must provide all PCR values when using TPM2 device key.");
} else { } else {
@ -4864,8 +4951,8 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
if (r < 0) if (r < 0)
return r; return r;
if (!tpm2_pcr_values_has_all_values(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values)) { if (!tpm2_pcr_values_has_all_values(pcr_values, n_pcr_values)) {
r = tpm2_pcr_read_missing_values(tpm2_context, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values); r = tpm2_pcr_read_missing_values(tpm2_context, pcr_values, n_pcr_values);
if (r < 0) if (r < 0)
return log_error_errno(r, "Could not read pcr values: %m"); return log_error_errno(r, "Could not read pcr values: %m");
} }
@ -4873,17 +4960,17 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
uint16_t hash_pcr_bank = 0; uint16_t hash_pcr_bank = 0;
uint32_t hash_pcr_mask = 0; uint32_t hash_pcr_mask = 0;
if (arg_tpm2_n_hash_pcr_values > 0) { if (n_pcr_values > 0) {
size_t hash_count; size_t hash_count;
r = tpm2_pcr_values_hash_count(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, &hash_count); r = tpm2_pcr_values_hash_count(pcr_values, n_pcr_values, &hash_count);
if (r < 0) if (r < 0)
return log_error_errno(r, "Could not get hash count: %m"); return log_error_errno(r, "Could not get hash count: %m");
if (hash_count > 1) if (hash_count > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected.");
hash_pcr_bank = arg_tpm2_hash_pcr_values[0].hash; hash_pcr_bank = pcr_values[0].hash;
r = tpm2_pcr_values_to_mask(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); r = tpm2_pcr_values_to_mask(pcr_values, n_pcr_values, hash_pcr_bank, &hash_pcr_mask);
if (r < 0) if (r < 0)
return log_error_errno(r, "Could not get hash mask: %m"); return log_error_errno(r, "Could not get hash mask: %m");
} }
@ -4896,8 +4983,8 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
/* If both PCR public key unlock and pcrlock unlock is selected, then shard the encryption key. */ /* If both PCR public key unlock and pcrlock unlock is selected, then shard the encryption key. */
r = tpm2_calculate_sealing_policy( r = tpm2_calculate_sealing_policy(
arg_tpm2_hash_pcr_values, pcr_values,
arg_tpm2_n_hash_pcr_values, n_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL, iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false, /* use_pin= */ false,
arg_tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL, arg_tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
@ -4907,8 +4994,8 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
if (arg_tpm2_pcrlock && iovec_is_set(&pubkey)) { if (arg_tpm2_pcrlock && iovec_is_set(&pubkey)) {
r = tpm2_calculate_sealing_policy( r = tpm2_calculate_sealing_policy(
arg_tpm2_hash_pcr_values, pcr_values,
arg_tpm2_n_hash_pcr_values, n_pcr_values,
/* public= */ NULL, /* Turn this one off for the 2nd shard */ /* public= */ NULL, /* Turn this one off for the 2nd shard */
/* use_pin= */ false, /* use_pin= */ false,
&pcrlock_policy, /* But turn this one on */ &pcrlock_policy, /* But turn this one on */
@ -8829,20 +8916,9 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY *
break; break;
case ARG_KEY_FILE: { case ARG_KEY_FILE: {
_cleanup_(erase_and_freep) char *k = NULL; r = parse_key_file(optarg, &arg_key);
size_t n = 0;
r = read_full_file_full(
AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX,
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET,
NULL,
&k, &n);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to read key file '%s': %m", optarg); return r;
erase_and_free(arg_key);
arg_key = TAKE_PTR(k);
arg_key_size = n;
break; break;
} }

View File

@ -9,6 +9,7 @@
#include "errno-util.h" #include "errno-util.h"
#include "glyph-util.h" #include "glyph-util.h"
#include "in-addr-util.h" #include "in-addr-util.h"
#include "iovec-util.h"
#include "json-util.h" #include "json-util.h"
#include "resolved-dns-answer.h" #include "resolved-dns-answer.h"
#include "resolved-dns-browse-services.h" #include "resolved-dns-browse-services.h"
@ -37,9 +38,8 @@ typedef struct LookupParameters {
int ifindex; int ifindex;
uint64_t flags; uint64_t flags;
int family; int family;
union in_addr_union address; struct iovec address;
size_t address_size; const char *name;
char *name;
uint16_t class; uint16_t class;
uint16_t type; uint16_t type;
} LookupParameters; } LookupParameters;
@ -62,7 +62,8 @@ typedef struct LookupParamatersBrowseServices {
static void lookup_parameters_destroy(LookupParameters *p) { static void lookup_parameters_destroy(LookupParameters *p) {
assert(p); assert(p);
free(p->name);
iovec_done(&p->address);
} }
static int dns_query_new_for_varlink( static int dns_query_new_for_varlink(
@ -342,7 +343,7 @@ static int parse_as_address(sd_varlink *link, LookupParameters *p) {
static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = { static const sd_json_dispatch_field dispatch_table[] = {
{ "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX }, { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX },
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LookupParameters, name), SD_JSON_MANDATORY }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, name), SD_JSON_MANDATORY },
{ "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParameters, family), 0 }, { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParameters, family), 0 },
{ "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 },
{} {}
@ -409,42 +410,6 @@ static int vl_method_resolve_hostname(sd_varlink *link, sd_json_variant *paramet
return 1; return 1;
} }
static int json_dispatch_address(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
LookupParameters *p = ASSERT_PTR(userdata);
union in_addr_union buf = {};
sd_json_variant *i;
size_t n, k = 0;
assert(variant);
if (!sd_json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
n = sd_json_variant_elements(variant);
if (!IN_SET(n, 4, 16))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name));
JSON_VARIANT_ARRAY_FOREACH(i, variant) {
int64_t b;
if (!sd_json_variant_is_integer(i))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an integer.", k, strna(name));
b = sd_json_variant_integer(i);
if (b < 0 || b > 0xff)
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
"Element %zu of JSON field '%s' is out of range 0%s255.",
k, strna(name), glyph(GLYPH_ELLIPSIS));
buf.bytes[k++] = (uint8_t) b;
}
p->address = buf;
p->address_size = k;
return 0;
}
static void vl_method_resolve_address_complete(DnsQuery *query) { static void vl_method_resolve_address_complete(DnsQuery *query) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
_cleanup_(dns_query_freep) DnsQuery *q = query; _cleanup_(dns_query_freep) DnsQuery *q = query;
@ -509,7 +474,7 @@ static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *paramete
static const sd_json_dispatch_field dispatch_table[] = { static const sd_json_dispatch_field dispatch_table[] = {
{ "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX }, { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX },
{ "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParameters, family), SD_JSON_MANDATORY }, { "family", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_int, offsetof(LookupParameters, family), SD_JSON_MANDATORY },
{ "address", SD_JSON_VARIANT_ARRAY, json_dispatch_address, 0, SD_JSON_MANDATORY }, { "address", SD_JSON_VARIANT_ARRAY, json_dispatch_byte_array_iovec, offsetof(LookupParameters, address), SD_JSON_MANDATORY },
{ "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 },
{} {}
}; };
@ -537,13 +502,15 @@ static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *paramete
if (!IN_SET(p.family, AF_INET, AF_INET6)) if (!IN_SET(p.family, AF_INET, AF_INET6))
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family"));
if (FAMILY_ADDRESS_SIZE(p.family) != p.address_size) if (FAMILY_ADDRESS_SIZE(p.family) != p.address.iov_len)
return sd_varlink_error(link, "io.systemd.Resolve.BadAddressSize", NULL); return sd_varlink_error(link, "io.systemd.Resolve.BadAddressSize", NULL);
if (validate_and_mangle_query_flags(m, &p.flags, /* name = */ NULL, /* ok = */ 0) < 0) if (validate_and_mangle_query_flags(m, &p.flags, /* name = */ NULL, /* ok = */ 0) < 0)
return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); return sd_varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags"));
r = dns_question_new_reverse(&question, p.family, &p.address); union in_addr_union a = IN_ADDR_NULL;
memcpy(&a, p.address.iov_base, p.address.iov_len);
r = dns_question_new_reverse(&question, p.family, &a);
if (r < 0) if (r < 0)
return r; return r;
@ -555,7 +522,7 @@ static int vl_method_resolve_address(sd_varlink *link, sd_json_variant *paramete
sd_varlink_set_userdata(link, q); sd_varlink_set_userdata(link, q);
q->request_family = p.family; q->request_family = p.family;
q->request_address = p.address; q->request_address = a;
q->complete = vl_method_resolve_address_complete; q->complete = vl_method_resolve_address_complete;
r = dns_query_go(q); r = dns_query_go(q);
@ -1138,7 +1105,7 @@ finish:
static int vl_method_resolve_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { static int vl_method_resolve_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = { static const sd_json_dispatch_field dispatch_table[] = {
{ "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX }, { "ifindex", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_ifindex, offsetof(LookupParameters, ifindex), SD_JSON_RELAX },
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(LookupParameters, name), SD_JSON_MANDATORY }, { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, name), SD_JSON_MANDATORY },
{ "class", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(LookupParameters, class), 0 }, { "class", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(LookupParameters, class), 0 },
{ "type", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(LookupParameters, type), SD_JSON_MANDATORY }, { "type", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint16, offsetof(LookupParameters, type), SD_JSON_MANDATORY },
{ "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, { "flags", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(LookupParameters, flags), 0 },

View File

@ -110,7 +110,7 @@ int umount_recursive_full(const char *prefix, int flags, char **keep) {
return n; return n;
} }
#define MS_CONVERTIBLE_FLAGS (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_NOSYMFOLLOW) #define MS_CONVERTIBLE_FLAGS (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_NOSYMFOLLOW|MS_RELATIME|MS_NOATIME|MS_STRICTATIME|MS_NODIRATIME)
static uint64_t ms_flags_to_mount_attr(unsigned long a) { static uint64_t ms_flags_to_mount_attr(unsigned long a) {
uint64_t f = 0; uint64_t f = 0;
@ -130,6 +130,35 @@ static uint64_t ms_flags_to_mount_attr(unsigned long a) {
if (FLAGS_SET(a, MS_NOSYMFOLLOW)) if (FLAGS_SET(a, MS_NOSYMFOLLOW))
f |= MOUNT_ATTR_NOSYMFOLLOW; f |= MOUNT_ATTR_NOSYMFOLLOW;
if (FLAGS_SET(a, MS_RELATIME))
f |= MOUNT_ATTR_RELATIME;
if (FLAGS_SET(a, MS_NOATIME))
f |= MOUNT_ATTR_NOATIME;
if (FLAGS_SET(a, MS_STRICTATIME))
f |= MOUNT_ATTR_STRICTATIME;
if (FLAGS_SET(a, MS_NODIRATIME))
f |= MOUNT_ATTR_NODIRATIME;
return f;
}
static uint64_t ms_flags_to_mount_attr_clr(unsigned long a) {
uint64_t f = 0;
/* As per documentation, if relatime/noatime/strictatime are set, we need to clear the atime flag
* too, otherwise -EINVAL will be returned by the kernel. */
if (FLAGS_SET(a, MS_RELATIME))
f |= MOUNT_ATTR__ATIME;
if (FLAGS_SET(a, MS_NOATIME))
f |= MOUNT_ATTR__ATIME;
if (FLAGS_SET(a, MS_STRICTATIME))
f |= MOUNT_ATTR__ATIME;
return f; return f;
} }
@ -1881,6 +1910,7 @@ int make_fsmount(
return log_full_errno(error_log_level, errno, "Failed to create mount fd for \"%s\" (\"%s\"): %m", what, type); return log_full_errno(error_log_level, errno, "Failed to create mount fd for \"%s\" (\"%s\"): %m", what, type);
struct mount_attr ma = { struct mount_attr ma = {
.attr_clr = ms_flags_to_mount_attr_clr(f),
.attr_set = ms_flags_to_mount_attr(f) | (userns_fd >= 0 ? MOUNT_ATTR_IDMAP : 0), .attr_set = ms_flags_to_mount_attr(f) | (userns_fd >= 0 ? MOUNT_ATTR_IDMAP : 0),
.userns_fd = userns_fd, .userns_fd = userns_fd,
}; };

View File

@ -2107,7 +2107,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
return log_oom(); return log_oom();
_cleanup_free_ char *image_fn = NULL; _cleanup_free_ char *image_fn = NULL;
if (path_extract_filename(arg_image, &image_fn) < 0) r = path_extract_filename(arg_image, &image_fn);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", image_fn); return log_error_errno(r, "Failed to extract filename from path '%s': %m", image_fn);
_cleanup_free_ char *escaped_image_fn = escape_qemu_value(image_fn); _cleanup_free_ char *escaped_image_fn = escape_qemu_value(image_fn);