diff --git a/.github/workflows/build-test.sh b/.github/workflows/build-test.sh index 9c3d2e73382e2..6d8324b9458f2 100755 --- a/.github/workflows/build-test.sh +++ b/.github/workflows/build-test.sh @@ -12,10 +12,10 @@ success() { echo >&2 -e "\033[32;1m$1\033[0m"; } ARGS=( "--optimization=0 -Dopenssl=disabled -Dtpm=true -Dtpm2=enabled" "--optimization=s -Dutmp=false -Dc_args='-DOPENSSL_NO_UI_CONSOLE=1'" - "--optimization=2 -Ddns-over-tls=openssl" + "--optimization=2 -Ddns-over-tls=openssl -Dc_args='-DOPENSSL_NO_DEPRECATED'" "--optimization=3 -Db_lto=true -Ddns-over-tls=false" "--optimization=3 -Db_lto=false -Dtpm2=disabled -Dlibfido2=disabled -Dp11kit=disabled -Defi=false -Dbootloader=disabled" - "--optimization=3 -Dfexecve=true -Dstandalone-binaries=true -Dstatic-libsystemd=true -Dstatic-libudev=true" + "--optimization=3 -Dfexecve=true -Dstandalone-binaries=true -Dstatic-libsystemd=true -Dstatic-libudev=true -Dsystemd-multicall-binary=true" "-Db_ndebug=true" "--auto-features=disabled -Dcompat-mutable-uid-boundaries=true" ) diff --git a/TODO.md b/TODO.md index 5537ba86aa358..5d89876dc20c5 100644 --- a/TODO.md +++ b/TODO.md @@ -938,16 +938,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later - drop nss-myhostname in favour of nss-resolve? -- drop NV_ORDERLY flag from the product uuid nvpcr. Effect of the flag is that - it pushes the thing into TPM RAM, but a TPM usually has very little of that, - less than NVRAM. hence setting the flag amplifies space issues. Unsetting the - flag increases wear issues on the NVRAM, however, but this should be limited - for the product uuid nvpcr, since its only changed once per boot. this needs - to be configurable by nvpcr however, as other nvpcrs are different, - i.e. verity one receives many writes during system uptime quite - possibly. (also, NV_ORDERLY makes stuff faster, and dropping it costs - possibly up to 100ms supposedly) - - **EFI:** - honor timezone efi variables for default timezone selection (if there are any?) diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 1d2498e206f6d..71df55896a550 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -76,6 +76,10 @@ All tools: `/etc/veritytab`. Only useful for debugging. Currently only supported by `systemd-veritysetup-generator`. +* `$SYSTEMD_CLONETAB` — if set, use this path instead of + `/etc/clonetab`. Only useful for debugging. Currently only supported by + `systemd-clonesetup-generator`. + * `$SYSTEMD_DEFAULT_HOSTNAME` — override the compiled-in fallback hostname (relevant in particular for the system manager and `systemd-hostnamed`). Must be a valid hostname (either a single label or a FQDN). diff --git a/docs/TPM2_PCR_MEASUREMENTS.md b/docs/TPM2_PCR_MEASUREMENTS.md index faa918f6319f6..87d8d3b675734 100644 --- a/docs/TPM2_PCR_MEASUREMENTS.md +++ b/docs/TPM2_PCR_MEASUREMENTS.md @@ -74,6 +74,18 @@ fields are: least important ones are skipped gracefully rather than the allocation failing arbitrarily. Ties are broken by name. Priority does not affect the NV index, the algorithm, or anything measured into the NvPCR. +* `orderly` — a boolean, defaulting to `true`. It controls whether the NV index + is allocated with the `TPMA_NV_ORDERLY` attribute set, which selects whether + the TPM keeps the NV index in RAM or in persistent memory (NVRAM). On + physical TPMs RAM is typically much more constrained than persistent memory, + but persistent memory is subject to wear. We hence prefer `TPMA_NV_ORDERLY` + disabled (i.e. NVRAM) for NvPCRs that are written only once each boot — which + translates into a conservative number of write cycles over the lifetime of a + TPM — but enabled (i.e. RAM) for NvPCRs we expect to be written many times + during runtime, so that we minimize wear. This reflects real-life experience + where the RAM in TPMs is so constrained that allocating many NvPCRs in TPM + RAM simply doesn't work. For now, only the `hardware` NvPCR (which is written + just once, early at boot) sets this flag to false. There's one complication: these NV indexes (like any NV indexes) can be deleted by anyone with access to the TPM, and then be recreated. This could be used to diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 0494f0311f0da..097714d8b897b 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -1432,6 +1432,10 @@ evdev:input:b0005v046DpB317* KEYBOARD_KEY_70047=brightnessdown KEYBOARD_KEY_70048=brightnessup +# Logitech K950 +evdev:input:b0005v046DpB388* + KEYBOARD_KEY_100e1=micmute # Mic-mute key emits BTN_0 + # iTouch evdev:input:b0003v046DpC308* KEYBOARD_KEY_90001=shop # Shopping diff --git a/hwdb.d/70-debug-appliance.hwdb b/hwdb.d/70-debug-appliance.hwdb index 8f535f750bb48..701df4b77d093 100644 --- a/hwdb.d/70-debug-appliance.hwdb +++ b/hwdb.d/70-debug-appliance.hwdb @@ -5,6 +5,16 @@ # Permitted keys: # ID_DEBUG_APPLIANCE=?* +# Amlogic devices in burn mode +# Used to interact with devices over Worldcup / ADNL protocol, see: +# https://wiki.coreelec.org/coreelec:aml_usb_tool +# +# The idVendor and idProduct used are documented in source code here: +# https://github.com/superna9999/pyamlboot/blob/d2857f4371bcdb7a90d885f24987e03b6d647a8a/PROTOCOL.md?plain=1#L17 +usb:v1B8EpC003* +usb:v1B8EpC004* + ID_DEBUG_APPLIANCE=aml_burn_mode + # Samsung devices in download mode # Used to interact with devices over Odin protocol, see: # https://en.wikipedia.org/wiki/Odin_(firmware_flashing_software) diff --git a/man/clonetab.xml b/man/clonetab.xml new file mode 100644 index 0000000000000..a22798dbf7ba8 --- /dev/null +++ b/man/clonetab.xml @@ -0,0 +1,121 @@ + + + + + + + + clonetab + systemd + + + + clonetab + 5 + + + + clonetab + Configuration for dm-clone block devices + + + + /etc/clonetab + + + + Description + + The /etc/clonetab file describes + dm-clone block devices that are set up during system boot. + + Empty lines and lines starting with the # + character are ignored. Each of the remaining lines describes one + dm-clone device. Fields are delimited by white space. + + Each line is in the formname source-device destination-device metadata-device [options] + The first four fields are mandatory, the fifth is optional. + + The five fields of /etc/clonetab are defined as follows: + + + + The first field contains the name of the resulting dm-clone device; its + block device is set up below /dev/mapper/. + + The second field contains a path to the read-only source block device. + This is the device whose data is cloned to the destination device. Reads to regions not + yet hydrated are served directly from this device. + + The third field contains a path to the writable destination block device. + The source device's data is copied here in the background. It must be at least as + large as the source device. + + The fourth field contains a path to the metadata block device. This + small device tracks which regions of the destination have been hydrated and is managed + exclusively by dm-clone. + + The fifth field, if present, contains comma-separated key=value + options. The following option is supported: + + + + + Controls the granularity of background hydration copying — how much + data is copied at a time. Region size is specified in bytes (standard suffixes like + K, M, G are supported), and must + correspond to a power of two between 4 KiB and 1 GiB. For example, + region_size=4K or region_size=4096 sets a 4 KiB region size. + + + One region is the atomic unit dm-clone tracks: it is either fully hydrated (copied to the destination) + or not, never partially. If a copy is interrupted mid-region, that whole region is + retried from scratch on next boot. Smaller regions mean finer progress tracking; + larger regions reduce metadata overhead. Defaults to 4K. For background, see + dm-clone kernel documentation. + + + + + + + If no options are needed, the field may be omitted entirely or + - may be used as a placeholder. + + + + + At early boot and when the system manager configuration is reloaded, this file is + translated into native systemd units by + systemd-clonesetup-generator8. + + + + Examples + + + Simple clone without options + Clone a source device to a destination, using a separate metadata device: + mydevice /dev/sdb /dev/sdc /dev/sdd + + + + Clone with custom region size + Clone a source device to a destination with a custom region size of 8 KiB: + mydevice /dev/sdb /dev/sdc /dev/sdd region_size=8K + + + + + See Also + + systemd1 + systemd-clonesetup-generator8 + dmsetup8 + + + + diff --git a/man/rules/meson.build b/man/rules/meson.build index 13025b8993cf7..bac836015b8fb 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -15,6 +15,7 @@ manpages = [ ['bootup', '7', [], ''], ['busctl', '1', [], ''], ['capsule@.service', '5', [], ''], + ['clonetab', '5', [], ''], ['coredump.conf', '5', ['coredump.conf.d'], 'ENABLE_COREDUMP'], ['coredumpctl', '1', [], 'ENABLE_COREDUMP'], ['crypttab', '5', [], 'HAVE_LIBCRYPTSETUP'], @@ -1024,6 +1025,11 @@ manpages = [ ['systemd-cat', '1', [], ''], ['systemd-cgls', '1', [], ''], ['systemd-cgtop', '1', [], ''], + ['systemd-clonesetup', + '8', + ['systemd-clonesetup@.service'], + ''], + ['systemd-clonesetup-generator', '8', [], ''], ['systemd-coredump', '8', ['systemd-coredump.socket', 'systemd-coredump@.service'], diff --git a/man/systemd-clonesetup-generator.xml b/man/systemd-clonesetup-generator.xml new file mode 100644 index 0000000000000..28c8b5ad004c4 --- /dev/null +++ b/man/systemd-clonesetup-generator.xml @@ -0,0 +1,50 @@ + + + + + + + + systemd-clonesetup-generator + systemd + + + + systemd-clonesetup-generator + 8 + + + + systemd-clonesetup-generator + Unit generator for /etc/clonetab + + + + /usr/lib/systemd/system-generators/systemd-clonesetup-generator + + + + Description + + systemd-clonesetup-generator is a generator that translates + /etc/clonetab into native systemd units early at boot and when + configuration of the system manager is reloaded. This will create + systemd-clonesetup@.service8 + units as necessary. + + systemd-clonesetup-generator implements + systemd.generator7. + + + + See Also + + systemd1 + clonetab5 + systemd-clonesetup@.service8 + dmsetup8 + + + + diff --git a/man/systemd-clonesetup.xml b/man/systemd-clonesetup.xml new file mode 100644 index 0000000000000..c488882db8881 --- /dev/null +++ b/man/systemd-clonesetup.xml @@ -0,0 +1,76 @@ + + + + + + + + systemd-clonesetup + systemd + + + + systemd-clonesetup + 8 + + + + systemd-clonesetup + systemd-clonesetup@.service + Set up and tear down dm-clone devices + + + + + systemd-clonesetup + add + NAME + SOURCE-DEVICE + DEST-DEVICE + META-DEVICE + OPTIONS + + + + systemd-clonesetup + remove + NAME + + + systemd-clonesetup@.service + + + + Description + + systemd-clonesetup is used to set up (with add) and tear down + (with remove) dm-clone devices. It is primarily used via + systemd-clonesetup@.service units generated from + /etc/clonetab by + systemd-clonesetup-generator8. + + + The positional arguments NAME, SOURCE-DEVICE, + DEST-DEVICE, META-DEVICE, and OPTIONS + have the same meaning as the fields in + clonetab5. + + Currently, the supported option in OPTIONS is + region_size=BYTES, where + BYTES is measured in bytes (standard suffixes like + K, M, G are supported). + + + + See Also + + systemd1 + clonetab5 + systemd-clonesetup-generator8 + systemd.generator7 + dmsetup8 + + + + diff --git a/meson.build b/meson.build index a5572301db03b..fc5a1b72bb6fa 100644 --- a/meson.build +++ b/meson.build @@ -302,6 +302,7 @@ conf.set_quoted('SYSTEMD_USERWORK_PATH', libexecdir / 'syst conf.set_quoted('SYSTEMD_MOUNTWORK_PATH', libexecdir / 'systemd-mountwork') conf.set_quoted('SYSTEMD_NSRESOURCEWORK_PATH', libexecdir / 'systemd-nsresourcework') conf.set_quoted('SYSTEMD_VERITYSETUP_PATH', libexecdir / 'systemd-veritysetup') +conf.set_quoted('SYSTEMD_CLONESETUP_PATH', libexecdir / 'systemd-clonesetup') conf.set_quoted('SYSTEM_CONFIG_UNIT_DIR', pkgsysconfdir / 'system') conf.set_quoted('SYSTEM_DATA_UNIT_DIR', systemunitdir) conf.set_quoted('SYSTEM_ENV_GENERATOR_DIR', systemenvgeneratordir) @@ -1028,13 +1029,18 @@ endif if get_option('libc') == 'musl' libcrypt = [] - libcrypt_cflags = [] + libcrypt_cflags = declare_dependency( + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBCRYPT', + ) have = get_option('libcrypt').allowed() else libcrypt = dependency('libcrypt', 'libxcrypt', required : get_option('libcrypt'), version : '>=4.4.0') - libcrypt_cflags = libcrypt.partial_dependency(includes: true, compile_args: true) + libcrypt_cflags = declare_dependency( + dependencies : libcrypt.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBCRYPT', + ) have = libcrypt.found() endif conf.set10('HAVE_LIBCRYPT', have) @@ -1052,7 +1058,10 @@ bpf_compiler = get_option('bpf-compiler') libbpf = dependency('libbpf', required : bpf_framework, version : bpf_compiler == 'gcc' ? '>= 1.4.0' : '>= 0.1.0') -libbpf_cflags = libbpf.partial_dependency(includes: true, compile_args: true) +libbpf_cflags = declare_dependency( + dependencies : libbpf.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBBPF', +) conf.set10('HAVE_LIBBPF', libbpf.found()) libmount = dependency('mount', @@ -1060,12 +1069,18 @@ libmount = dependency('mount', required : get_option('libmount')) have = libmount.found() conf.set10('HAVE_LIBMOUNT', have) -libmount_cflags = libmount.partial_dependency(includes: true, compile_args: true) +libmount_cflags = declare_dependency( + dependencies : libmount.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBMOUNT', +) libfdisk = dependency('fdisk', version : '>= 2.35', required : get_option('fdisk')) -libfdisk_cflags = libfdisk.partial_dependency(includes: true, compile_args: true) +libfdisk_cflags = declare_dependency( + dependencies : libfdisk.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBFDISK', +) conf.set10('HAVE_LIBFDISK', libfdisk.found()) # This prefers pwquality if both are enabled or auto. @@ -1079,7 +1094,10 @@ if not have libpwquality = dependency('passwdqc', required : get_option('passwdqc')) endif -libpwquality_cflags = libpwquality.partial_dependency(includes: true, compile_args: true) +libpwquality_cflags = declare_dependency( + dependencies : libpwquality.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBPWQUALITY', +) conf.set10('HAVE_PWQUALITY', have) conf.set10('HAVE_PASSWDQC', not have and libpwquality.found()) @@ -1087,19 +1105,28 @@ libseccomp = dependency('libseccomp', version : '>= 2.4.0', required : get_option('seccomp')) conf.set10('HAVE_SECCOMP', libseccomp.found()) -libseccomp_cflags = libseccomp.partial_dependency(includes: true, compile_args: true) +libseccomp_cflags = declare_dependency( + dependencies : libseccomp.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBSECCOMP', +) libselinux = dependency('libselinux', version : '>= 2.1.9', required : get_option('selinux')) conf.set10('HAVE_SELINUX', libselinux.found()) -libselinux_cflags = libselinux.partial_dependency(includes: true, compile_args: true) +libselinux_cflags = declare_dependency( + dependencies : libselinux.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBSELINUX', +) libapparmor = dependency('libapparmor', version : '>= 2.13', required : get_option('apparmor')) conf.set10('HAVE_APPARMOR', libapparmor.found()) -libapparmor_cflags = libapparmor.partial_dependency(includes: true, compile_args: true) +libapparmor_cflags = declare_dependency( + dependencies : libapparmor.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBAPPARMOR', +) have = get_option('smack') and get_option('smack-run-label') != '' conf.set10('HAVE_SMACK_RUN_LABEL', have) @@ -1125,30 +1152,45 @@ conf.set10('ENABLE_POLKIT', install_polkit) libacl = dependency('libacl', required : get_option('acl')) conf.set10('HAVE_ACL', libacl.found()) -libacl_cflags = libacl.partial_dependency(includes: true, compile_args: true) +libacl_cflags = declare_dependency( + dependencies : libacl.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBACL', +) libaudit = dependency('audit', required : get_option('audit')) conf.set10('HAVE_AUDIT', libaudit.found()) -libaudit_cflags = libaudit.partial_dependency(includes: true, compile_args: true) +libaudit_cflags = declare_dependency( + dependencies : libaudit.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBAUDIT', +) libblkid = dependency('blkid', version : '>=2.37.0', required : get_option('blkid')) conf.set10('HAVE_BLKID', libblkid.found()) -libblkid_cflags = libblkid.partial_dependency(includes: true, compile_args: true) +libblkid_cflags = declare_dependency( + dependencies : libblkid.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBBLKID', +) libkmod = dependency('libkmod', version : '>= 15', required : get_option('kmod')) conf.set10('HAVE_KMOD', libkmod.found()) -libkmod_cflags = libkmod.partial_dependency(includes: true, compile_args: true) +libkmod_cflags = declare_dependency( + dependencies : libkmod.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBKMOD', +) libxenctrl = dependency('xencontrol', version : '>= 4.9', required : get_option('xenctrl')) conf.set10('HAVE_XENCTRL', libxenctrl.found()) -libxenctrl_cflags = libxenctrl.partial_dependency(includes: true, compile_args: true) +libxenctrl_cflags = declare_dependency( + dependencies : libxenctrl.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBXENCTRL', +) feature = get_option('pam') libpam = dependency('pam', @@ -1158,13 +1200,19 @@ if not libpam.found() libpam = cc.find_library('pam', required : feature) endif conf.set10('HAVE_PAM', libpam.found()) -libpam_cflags = libpam.partial_dependency(includes: true, compile_args: true) +libpam_cflags = declare_dependency( + dependencies : libpam.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBPAM', +) libmicrohttpd = dependency('libmicrohttpd', version : '>= 0.9.33', required : get_option('microhttpd')) conf.set10('HAVE_MICROHTTPD', libmicrohttpd.found()) -libmicrohttpd_cflags = libmicrohttpd.partial_dependency(includes: true, compile_args: true) +libmicrohttpd_cflags = declare_dependency( + dependencies : libmicrohttpd.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBMICROHTTPD', +) libcryptsetup = get_option('libcryptsetup') libcryptsetup_plugins = get_option('libcryptsetup-plugins') @@ -1178,7 +1226,10 @@ endif libcryptsetup = dependency('libcryptsetup', version : '>= 2.4.0', required : libcryptsetup) -libcryptsetup_cflags = libcryptsetup.partial_dependency(includes: true, compile_args: true) +libcryptsetup_cflags = declare_dependency( + dependencies : libcryptsetup.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBCRYPTSETUP', +) have = libcryptsetup.found() conf.set10('HAVE_LIBCRYPTSETUP', have) @@ -1188,19 +1239,28 @@ conf.set10('HAVE_LIBCRYPTSETUP_PLUGINS', libcurl = dependency('libcurl', version : '>= 7.32.0', required : get_option('libcurl')) -libcurl_cflags = libcurl.partial_dependency(includes: true, compile_args: true) +libcurl_cflags = declare_dependency( + dependencies : libcurl.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBCURL', +) conf.set10('HAVE_LIBCURL', libcurl.found()) conf.set10('CURL_NO_OLDIES', conf.get('BUILD_MODE_DEVELOPER') == 1) libidn2 = dependency('libidn2', required : get_option('libidn2')) -libidn2_cflags = libidn2.partial_dependency(includes: true, compile_args: true) +libidn2_cflags = declare_dependency( + dependencies : libidn2.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBIDN2', +) conf.set10('HAVE_LIBIDN2', libidn2.found()) libqrencode = dependency('libqrencode', version : '>= 3', required : get_option('qrencode')) -libqrencode_cflags = libqrencode.partial_dependency(includes: true, compile_args: true) +libqrencode_cflags = declare_dependency( + dependencies : libqrencode.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBQRENCODE', +) conf.set10('HAVE_QRENCODE', libqrencode.found()) feature = get_option('gcrypt') @@ -1208,7 +1268,10 @@ libgcrypt = dependency('libgcrypt', required : feature.disabled() ? feature : false) conf.set10('HAVE_GCRYPT', libgcrypt.found()) if libgcrypt.found() - libgcrypt_cflags = libgcrypt.partial_dependency(includes: true, compile_args: true) + libgcrypt_cflags = declare_dependency( + dependencies : libgcrypt.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBGCRYPT', + ) else libgcrypt_cflags = [] endif @@ -1217,48 +1280,78 @@ libgnutls = dependency('gnutls', version : '>= 3.1.4', required : get_option('gnutls')) conf.set10('HAVE_GNUTLS', libgnutls.found()) -libgnutls_cflags = libgnutls.partial_dependency(includes: true, compile_args: true) +libgnutls_cflags = declare_dependency( + dependencies : libgnutls.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBGNUTLS', +) libopenssl = dependency('openssl', version : '>= 3.0.0', required : get_option('openssl')) -libopenssl_cflags = libopenssl.partial_dependency(includes: true, compile_args: true) +libopenssl_cflags = declare_dependency( + dependencies : libopenssl.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBOPENSSL', +) conf.set10('HAVE_OPENSSL', libopenssl.found()) libp11kit = dependency('p11-kit-1', version : '>= 0.23.3', required : get_option('p11kit')) conf.set10('HAVE_P11KIT', libp11kit.found()) -libp11kit_cflags = libp11kit.partial_dependency(includes: true, compile_args: true) +libp11kit_cflags = declare_dependency( + dependencies : libp11kit.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBP11KIT', +) feature = get_option('libfido2').require( conf.get('HAVE_OPENSSL') == 1, error_message : 'openssl required') libfido2 = dependency('libfido2', required : feature) -libfido2_cflags = libfido2.partial_dependency(includes: true, compile_args: true) +libfido2_cflags = declare_dependency( + dependencies : libfido2.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBFIDO2', +) conf.set10('HAVE_LIBFIDO2', libfido2.found()) -tpm2 = dependency('tss2-esys tss2-rc tss2-mu tss2-tcti-device', - required : get_option('tpm2')) -tpm2_cflags = tpm2.partial_dependency(includes: true, compile_args: true) -conf.set10('HAVE_TPM2', tpm2.found()) -conf.set10('HAVE_TSS2_ESYS3', tpm2.found() and tpm2.version().version_compare('>= 3.0.0')) +tss2_esys = dependency('tss2-esys', required : get_option('tpm2')) +tss2_mu = dependency('tss2-mu', required : get_option('tpm2')) +tss2_rc = dependency('tss2-rc', required : get_option('tpm2')) +tss2_tcti_device = dependency('tss2-tcti-device', required : get_option('tpm2')) +tpm2_cflags = declare_dependency( + dependencies : [ + tss2_esys.partial_dependency(includes: true, compile_args: true), + tss2_mu.partial_dependency(includes: true, compile_args: true), + tss2_rc.partial_dependency(includes: true, compile_args: true), + tss2_tcti_device.partial_dependency(includes: true, compile_args: true), + ], + compile_args : '-DSYSTEMD_CFLAGS_MARKER_TPM2', +) +conf.set10('HAVE_TPM2', tss2_esys.found() and tss2_mu.found() and tss2_rc.found() and tss2_tcti_device.found()) +conf.set10('HAVE_TSS2_ESYS3', tss2_esys.found() and tss2_esys.version().version_compare('>= 3.0.0')) conf.set('TPM2_NVPCR_BASE', get_option('tpm2-nvpcr-base')) libdw = dependency('libdw', version : '>=0.177', required : get_option('elfutils')) -libdw_cflags = libdw.partial_dependency(includes: true, compile_args: true) libelf = dependency('libelf', required : get_option('elfutils')) -libelf_cflags = libelf.partial_dependency(includes: true, compile_args: true) +libelf_cflags = declare_dependency( + dependencies : [ + libelf.partial_dependency(includes: true, compile_args: true), + libdw.partial_dependency(includes: true, compile_args: true), + ], + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBELF', +) conf.set10('HAVE_ELFUTILS', libdw.found() and libelf.found()) libz = dependency('zlib', required : get_option('zlib')) conf.set10('HAVE_ZLIB', libz.found()) -libz_cflags = libz.partial_dependency(includes: true, compile_args: true) +libz_cflags = declare_dependency( + dependencies : libz.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBZ', +) feature = get_option('bzip2') libbzip2 = dependency('bzip2', @@ -1268,24 +1361,36 @@ if not libbzip2.found() libbzip2 = cc.find_library('bz2', required : feature) endif conf.set10('HAVE_BZIP2', libbzip2.found()) -libbzip2_cflags = libbzip2.partial_dependency(includes: true, compile_args: true) +libbzip2_cflags = declare_dependency( + dependencies : libbzip2.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBBZIP2', +) libxz = dependency('liblzma', required : get_option('xz')) conf.set10('HAVE_XZ', libxz.found()) -libxz_cflags = libxz.partial_dependency(includes: true, compile_args: true) +libxz_cflags = declare_dependency( + dependencies : libxz.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBXZ', +) liblz4 = dependency('liblz4', version : '>= 1.3.0', required : get_option('lz4')) conf.set10('HAVE_LZ4', liblz4.found()) -liblz4_cflags = liblz4.partial_dependency(includes: true, compile_args: true) +liblz4_cflags = declare_dependency( + dependencies : liblz4.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBLZ4', +) libzstd = dependency('libzstd', version : '>= 1.4.0', required : get_option('zstd')) conf.set10('HAVE_ZSTD', libzstd.found()) -libzstd_cflags = libzstd.partial_dependency(includes: true, compile_args: true) +libzstd_cflags = declare_dependency( + dependencies : libzstd.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBZSTD', +) conf.set10('HAVE_COMPRESSION', libxz.found() or liblz4.found() or libzstd.found()) @@ -1321,18 +1426,27 @@ conf.set('DEFAULT_COMPRESSION', 'COMPRESSION_@0@'.format(compression.to_upper()) libarchive = dependency('libarchive', version : '>= 3.0', required : get_option('libarchive')) -libarchive_cflags = libarchive.partial_dependency(includes: true, compile_args: true) +libarchive_cflags = declare_dependency( + dependencies : libarchive.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBARCHIVE', +) conf.set10('HAVE_LIBARCHIVE', libarchive.found()) libxkbcommon = dependency('xkbcommon', version : '>= 0.3.0', required : get_option('xkbcommon')) -libxkbcommon_cflags = libxkbcommon.partial_dependency(includes: true, compile_args: true) +libxkbcommon_cflags = declare_dependency( + dependencies : libxkbcommon.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBXKBCOMMON', +) conf.set10('HAVE_XKBCOMMON', libxkbcommon.found()) libpcre2 = dependency('libpcre2-8', required : get_option('pcre2')) -libpcre2_cflags = libpcre2.partial_dependency(includes: true, compile_args: true) +libpcre2_cflags = declare_dependency( + dependencies : libpcre2.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBPCRE2', +) conf.set10('HAVE_PCRE2', libpcre2.found()) libglib = dependency('glib-2.0', @@ -1344,15 +1458,23 @@ libgobject = dependency('gobject-2.0', libgio = dependency('gio-2.0', required : get_option('glib')) conf.set10('HAVE_GLIB', libglib.found() and libgobject.found() and libgio.found()) -libglib_cflags = libglib.partial_dependency(includes: true, compile_args: true) -libgobject_cflags = libgobject.partial_dependency(includes: true, compile_args: true) -libgio_cflags = libgio.partial_dependency(includes: true, compile_args: true) +libglib_cflags = declare_dependency( + dependencies : [ + libglib.partial_dependency(includes: true, compile_args: true), + libgobject.partial_dependency(includes: true, compile_args: true), + libgio.partial_dependency(includes: true, compile_args: true), + ], + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBGLIB', +) libdbus = dependency('dbus-1', version : '>= 1.3.2', required : get_option('dbus')) conf.set10('HAVE_DBUS', libdbus.found()) -libdbus_cflags = libdbus.partial_dependency(includes: true, compile_args: true) +libdbus_cflags = declare_dependency( + dependencies : libdbus.partial_dependency(includes: true, compile_args: true), + compile_args : '-DSYSTEMD_CFLAGS_MARKER_LIBDBUS', +) dbusdatadir = libdbus.get_variable(pkgconfig: 'datadir', default_value: datadir) / 'dbus-1' @@ -1811,12 +1933,16 @@ if static_libsystemd != 'false' install_tag: 'libsystemd', install_dir : libdir, pic : static_libsystemd_pic, - dependencies : [liblz4_cflags, - libm, - libucontext, - libxz_cflags, - libzstd_cflags, - userspace], + dependencies : [ + libbzip2_cflags, + liblz4_cflags, + libm, + libucontext, + libxz_cflags, + libz_cflags, + libzstd_cflags, + userspace, + ], c_args : libsystemd_c_args + (static_libsystemd_pic ? [] : ['-fno-PIC'])) alias_target('libsystemd', libsystemd, install_libsystemd_static) @@ -1854,8 +1980,15 @@ if static_libudev != 'false' install_tag: 'libudev', install_dir : libdir, link_depends : libudev_sym, - dependencies : [libshared_deps, - userspace], + dependencies : [ + libbzip2_cflags, + liblz4_cflags, + libshared_deps, + libxz_cflags, + libz_cflags, + libzstd_cflags, + userspace, + ], c_args : static_libudev_pic ? [] : ['-fno-PIC'], pic : static_libudev_pic) @@ -1997,6 +2130,7 @@ subdir('src/bootctl') subdir('src/busctl') subdir('src/cgls') subdir('src/cgtop') +subdir('src/clonesetup') subdir('src/coredump') subdir('src/creds') subdir('src/cryptenroll') diff --git a/po/cs.po b/po/cs.po index 76c4e7ba623cb..64cc38081f8b5 100644 --- a/po/cs.po +++ b/po/cs.po @@ -6,12 +6,13 @@ # Pavel Borecki , 2023, 2024, 2025, 2026. # Jan Kalabza , 2025, 2026. # Honza Hejzl , 2026. +# Michal Čihař , 2026. msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-01 11:49+0200\n" -"PO-Revision-Date: 2026-06-07 14:00+0000\n" -"Last-Translator: Jan Kalabza \n" +"PO-Revision-Date: 2026-07-03 16:17+0000\n" +"Last-Translator: Michal Čihař \n" "Language-Team: Czech \n" "Language: cs\n" @@ -20,7 +21,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" -"X-Generator: Weblate 2026.6.1\n" +"X-Generator: Weblate 2026.7.1.dev0\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1384,11 +1385,10 @@ msgid "Please verify user on security token to unlock." msgstr "" #: src/shared/libfido2-util.c:936 -#, fuzzy, c-format -#| msgid "Please enter security token PIN:" +#, c-format msgid "" "Please enter security token PIN (remaining attempts before lock-out: %d):" -msgstr "Zadejte PIN bezpečnostního tokenu:" +msgstr "Zadejte PIN bezpečnostního tokenu (zbývající pokusy před zamčením: %d):" #: src/shared/libfido2-util.c:948 msgid "Please enter security token PIN:" diff --git a/po/fr.po b/po/fr.po index 21cc88571d563..aea53d70f18d5 100644 --- a/po/fr.po +++ b/po/fr.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-01 11:49+0200\n" -"PO-Revision-Date: 2026-06-09 09:04+0000\n" +"PO-Revision-Date: 2026-07-03 16:17+0000\n" "Last-Translator: Léane GRASSER \n" "Language-Team: French \n" @@ -21,7 +21,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 2026.6.1\n" +"X-Generator: Weblate 2026.7.1.dev0\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1489,25 +1489,26 @@ msgstr "" #: src/shared/libfido2-util.c:500 src/shared/libfido2-util.c:557 msgid "Please confirm presence on security token to unlock." msgstr "" -"Veuillez confirmer votre présence sur la clef de sécurité pour le " +"Confirmez votre présence sur la clé de sécurité pour procéder au " "déverrouillage." #: src/shared/libfido2-util.c:516 msgid "Please verify user on security token to unlock." msgstr "" -"Veuillez confirmer l'utilisateur sur la clef de sécurité pour le " +"Confirmez l'utilisateur sur la clé de sécurité pour procéder au " "déverrouillage." #: src/shared/libfido2-util.c:936 -#, fuzzy, c-format -#| msgid "Please enter security token PIN:" +#, c-format msgid "" "Please enter security token PIN (remaining attempts before lock-out: %d):" -msgstr "Veuillez entrer le code PIN de la clef de sécurité:" +msgstr "" +"Saisissez le PIN de la clé de sécurité (tentatives restantes avant blocage : " +"%d) :" #: src/shared/libfido2-util.c:948 msgid "Please enter security token PIN:" -msgstr "Veuillez entrer le code PIN de la clef de sécurité:" +msgstr "Saisissez le PIN de la clé de sécurité :" #~ msgid "Cleanup old system updates" #~ msgstr "Nettoyer les anciennes mises à jour système" diff --git a/po/zh_CN.po b/po/zh_CN.po index 35ad8424ce74d..5d7e710a59460 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -18,8 +18,8 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2026-06-01 11:49+0200\n" -"PO-Revision-Date: 2026-06-12 09:22+0000\n" -"Last-Translator: Luke Na \n" +"PO-Revision-Date: 2026-07-03 16:17+0000\n" +"Last-Translator: Jesse Guo \n" "Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" @@ -27,7 +27,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 2026.6.1\n" +"X-Generator: Weblate 2026.7.1.dev0\n" #: src/core/org.freedesktop.systemd1.policy.in:22 msgid "Send passphrase back to system" @@ -1302,11 +1302,10 @@ msgid "Please verify user on security token to unlock." msgstr "请在安全令牌上验证用户身份以解锁。" #: src/shared/libfido2-util.c:936 -#, fuzzy, c-format -#| msgid "Please enter security token PIN:" +#, c-format msgid "" "Please enter security token PIN (remaining attempts before lock-out: %d):" -msgstr "请输入安全令牌 PIN:" +msgstr "请输入安全令牌 PIN(锁定前剩余尝试次数:%d):" #: src/shared/libfido2-util.c:948 msgid "Please enter security token PIN:" diff --git a/src/analyze/analyze-calendar.c b/src/analyze/analyze-calendar.c index a9fb1e897e67e..f6d434de36f58 100644 --- a/src/analyze/analyze-calendar.c +++ b/src/analyze/analyze-calendar.c @@ -19,7 +19,7 @@ static int test_calendar_one(usec_t n, const char *p) { TableCell *cell; int r; - r = calendar_spec_from_string(p, &spec); + r = calendar_spec_from_string_full(p, &spec, /* warn_on_weekday_mismatch= */ true); if (r < 0) { log_error_errno(r, "Failed to parse calendar specification '%s': %m", p); time_parsing_hint(p, /* calendar= */ false, /* timestamp= */ true, /* timespan= */ true); diff --git a/src/analyze/meson.build b/src/analyze/meson.build index 67001845ac181..a8232eb851cd2 100644 --- a/src/analyze/meson.build +++ b/src/analyze/meson.build @@ -55,7 +55,10 @@ executables += [ libcore, libshared, ], - 'dependencies' : libseccomp_cflags, + 'dependencies' : [ + libseccomp_cflags, + tpm2_cflags, + ], 'install' : conf.get('ENABLE_ANALYZE') == 1, }, core_test_template + { diff --git a/src/basic/compress.c b/src/basic/compress.c index d75065781820f..70d0b7ced319c 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -6,25 +6,40 @@ #include #if HAVE_XZ +#ifndef SYSTEMD_CFLAGS_MARKER_LIBXZ +# error "missing libxz_cflags in meson dependency." +#endif #include #endif #if HAVE_LZ4 +#ifndef SYSTEMD_CFLAGS_MARKER_LIBLZ4 +# error "missing liblz4_cflags in meson dependency." +#endif #include #include #include #endif #if HAVE_ZSTD +#ifndef SYSTEMD_CFLAGS_MARKER_LIBZSTD +# error "missing libzstd_cflags in meson dependency." +#endif #include #include #endif #if HAVE_ZLIB +#ifndef SYSTEMD_CFLAGS_MARKER_LIBZ +# error "missing libz_cflags in meson dependency." +#endif #include #endif #if HAVE_BZIP2 +#ifndef SYSTEMD_CFLAGS_MARKER_LIBBZIP2 +# error "missing libbzip2_cflags in meson dependency." +#endif #include #endif diff --git a/src/basic/memory-util.c b/src/basic/memory-util.c index f03ed7adbbe26..10a5157ca6466 100644 --- a/src/basic/memory-util.c +++ b/src/basic/memory-util.c @@ -19,22 +19,6 @@ size_t page_size(void) { return pgsz; } -void* memdup_reverse(const void *mem, size_t size) { - assert(mem); - assert(size != 0); - - void *p = malloc(size); - if (!p) - return NULL; - - uint8_t *p_dst = p; - const uint8_t *p_src = mem; - for (size_t i = 0, k = size; i < size; i++, k--) - p_dst[i] = p_src[k-1]; - - return p; -} - void* erase_and_free(void *p) { size_t l; diff --git a/src/basic/memory-util.h b/src/basic/memory-util.h index 4caf58585a779..39dff8271a217 100644 --- a/src/basic/memory-util.h +++ b/src/basic/memory-util.h @@ -98,6 +98,3 @@ static inline void erase_and_freep(void *p) { static inline void erase_char(char *p) { explicit_bzero_safe(p, sizeof(char)); } - -/* Makes a copy of the buffer with reversed order of bytes */ -void* memdup_reverse(const void *mem, size_t size); diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 93173c4d917f3..44c5306741174 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -321,24 +321,26 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) { return tv; } +/* Returns the abbreviated English weekday name for wd in Mon=0 … Sun=6 order. + * We use non-localized (English) form so that timestamps can be parsed with + * parse_timestamp() and always read the same regardless of locale. */ +static const char *const weekday_table[_WEEKDAY_MAX] = { + [WEEKDAY_MON] = "Mon", + [WEEKDAY_TUE] = "Tue", + [WEEKDAY_WED] = "Wed", + [WEEKDAY_THU] = "Thu", + [WEEKDAY_FRI] = "Fri", + [WEEKDAY_SAT] = "Sat", + [WEEKDAY_SUN] = "Sun", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(weekday, int); + char* format_timestamp_style( char *buf, size_t l, usec_t t, TimestampStyle style) { - - /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that - * our generated timestamps may be parsed with parse_timestamp(), and always read the same. */ - static const char * const weekdays[] = { - [0] = "Sun", - [1] = "Mon", - [2] = "Tue", - [3] = "Wed", - [4] = "Thu", - [5] = "Fri", - [6] = "Sat", - }; - struct tm tm; bool utc, us; size_t n; @@ -387,8 +389,9 @@ char* format_timestamp_style( return NULL; /* Start with the week day */ - assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays)); - memcpy(buf, weekdays[tm.tm_wday], 4); + const char *weekday = weekday_to_string(tm.tm_wday == 0 ? 6 : tm.tm_wday - 1); + assert(weekday); + memcpy(buf, weekday, 4); if (style == TIMESTAMP_DATE) { /* Special format string if only date should be shown. */ diff --git a/src/basic/time-util.h b/src/basic/time-util.h index fb0aab3ff5c38..12004497289d7 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -56,6 +56,18 @@ typedef enum TimestampStyle { #define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC)) #define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC)) +enum { + WEEKDAY_MON, + WEEKDAY_TUE, + WEEKDAY_WED, + WEEKDAY_THU, + WEEKDAY_FRI, + WEEKDAY_SAT, + WEEKDAY_SUN, + _WEEKDAY_MAX, + _WEEKDAY_INVALID = -EINVAL, +}; + /* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this * to 6. Let's rely on that. */ #define FORMAT_TIMESTAMP_MAX (3U+1U+10U+1U+8U+1U+6U+1U+6U+1U) @@ -125,6 +137,10 @@ char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) _warn_unused_result_; char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_; +/* Returns the abbreviated English weekday name for wd in Mon=0 … Sun=6 order + * (matching systemd's weekdays_bits layout). */ +const char* weekday_to_string(int i); + _warn_unused_result_ static inline char* format_timestamp_relative(char *buf, size_t l, usec_t t) { return format_timestamp_relative_full(buf, l, t, CLOCK_REALTIME, /* implicit_left= */ false); diff --git a/src/bless-boot/bless-boot.c b/src/bless-boot/bless-boot.c index 43fb72cddb72b..4242182ac10b4 100644 --- a/src/bless-boot/bless-boot.c +++ b/src/bless-boot/bless-boot.c @@ -455,7 +455,7 @@ VERB_FULL(verb_set, "indeterminate", NULL, VERB_ANY, 1, 0, STATUS_INDETERMINATE, "Undo any marking as good or bad"); static int verb_set(int argc, char *argv[], uintptr_t data, void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; - const char *target, *source1, *source2; + const char *target = NULL, *source1 = NULL, *source2 = NULL; /* avoid false maybe-uninitialized warning */ uint64_t left, done; Status status = data; int r; diff --git a/src/boot/boot.c b/src/boot/boot.c index dbe7f0c8ab60c..c2f7e9b5008de 100644 --- a/src/boot/boot.c +++ b/src/boot/boot.c @@ -2046,8 +2046,8 @@ static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { return false; _cleanup_free_ PeSectionHeader *section_table = NULL; - size_t n_section_table; - err = pe_section_table_from_file(handle, §ion_table, &n_section_table); + size_t n_section_table, size_in_memory; + err = pe_section_table_from_file(handle, §ion_table, &n_section_table, &size_in_memory); if (err != EFI_SUCCESS) return false; @@ -2058,6 +2058,7 @@ static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { section_names, /* profile= */ UINT_MAX, /* validate_base= */ 0, + size_in_memory, vector); if (vector[0].memory_size != STRLEN(SD_MAGIC)) return false; @@ -2320,8 +2321,8 @@ static void boot_entry_add_type2( /* Load section table once */ _cleanup_free_ PeSectionHeader *section_table = NULL; - size_t n_section_table; - err = pe_section_table_from_file(handle, §ion_table, &n_section_table); + size_t n_section_table, size_in_memory; + err = pe_section_table_from_file(handle, §ion_table, &n_section_table, &size_in_memory); if (err != EFI_SUCCESS) return; @@ -2333,6 +2334,7 @@ static void boot_entry_add_type2( section_names, /* profile= */ UINT_MAX, /* validate_base= */ 0, + size_in_memory, base_sections); /* and now iterate through possible profiles, and create a menu item for each profile we find */ @@ -2348,6 +2350,7 @@ static void boot_entry_add_type2( section_names, profile, /* validate_base= */ 0, + size_in_memory, sections); if (err != EFI_SUCCESS && profile > 0) /* It's fine if there's no .profile for the first profile */ @@ -3100,7 +3103,7 @@ static EFI_STATUS call_image_start( if (err == EFI_UNSUPPORTED && entry->type == LOADER_LINUX) { uint32_t compat_address; - err = pe_kernel_info(loaded_image->ImageBase, /* ret_entry_point= */ NULL, &compat_address, + err = pe_kernel_info(loaded_image->ImageBase, loaded_image->ImageSize, /* ret_entry_point= */ NULL, &compat_address, /* ret_size_in_memory= */ NULL, /* ret_section_alignment= */ NULL); if (err != EFI_SUCCESS) { diff --git a/src/boot/device-path-util.c b/src/boot/device-path-util.c index 5243e81e5df18..73c3933299c07 100644 --- a/src/boot/device-path-util.c +++ b/src/boot/device-path-util.c @@ -25,6 +25,10 @@ EFI_STATUS make_file_device_path(EFI_HANDLE device, const char16_t *file, EFI_DE const EFI_DEVICE_PATH *end_node = device_path_find_end_node(dp); size_t file_size = strsize16(file); + /* The node Length is a uint16_t, so refuse a path that would not fit. */ + if (file_size > UINT16_MAX - sizeof(FILEPATH_DEVICE_PATH)) + return EFI_INVALID_PARAMETER; + size_t dp_size = (uint8_t *) end_node - (uint8_t *) dp; /* Make a copy that can also hold a file media device path. */ @@ -55,6 +59,9 @@ EFI_STATUS make_url_device_path(const char16_t *url, EFI_DEVICE_PATH **ret) { return EFI_INVALID_PARAMETER; size_t l = strlen8(u); + /* The node Length is a uint16_t, so refuse a URL that would not fit. */ + if (l > UINT16_MAX - offsetof(URI_DEVICE_PATH, Uri)) + return EFI_INVALID_PARAMETER; size_t t = offsetof(URI_DEVICE_PATH, Uri) + l + sizeof(EFI_DEVICE_PATH); EFI_DEVICE_PATH *dp = xmalloc(t); @@ -177,6 +184,23 @@ EFI_DEVICE_PATH *device_path_replace_node( return ret; } +bool device_path_is_valid(const EFI_DEVICE_PATH *dp, size_t size) { + if (!dp) + return false; + + /* Validate against the known size so a truncated/corrupt path can't make us read out of bounds. */ + for (;;) { + if (size < sizeof(EFI_DEVICE_PATH)) + return false; + if (dp->Length < sizeof(EFI_DEVICE_PATH) || dp->Length > size) + return false; + if (device_path_is_end(dp)) + return true; + size -= dp->Length; + dp = (const EFI_DEVICE_PATH *) ((const uint8_t *) dp + dp->Length); + } +} + size_t device_path_size(const EFI_DEVICE_PATH *dp) { const EFI_DEVICE_PATH *i = ASSERT_PTR(dp); diff --git a/src/boot/device-path-util.h b/src/boot/device-path-util.h index b02cf3146a9ec..8a993f27fe909 100644 --- a/src/boot/device-path-util.h +++ b/src/boot/device-path-util.h @@ -13,6 +13,9 @@ EFI_DEVICE_PATH *device_path_replace_node( static inline EFI_DEVICE_PATH *device_path_next_node(const EFI_DEVICE_PATH *dp) { assert(dp); + /* The node Length includes the 4-byte header, so a well-formed node is at least that long. Paths + * coming from untrusted sources must be checked with device_path_is_valid() before being walked. */ + assert(dp->Length >= sizeof(EFI_DEVICE_PATH)); return (EFI_DEVICE_PATH *) ((uint8_t *) dp + dp->Length); } @@ -30,4 +33,8 @@ static inline bool device_path_is_end(const EFI_DEVICE_PATH *dp) { size_t device_path_size(const EFI_DEVICE_PATH *dp); +/* Validates that a device path is well-formed and fully contained within the given size, terminated by an + * end node. Use on paths from untrusted sources (e.g. EFI variables) before walking them. */ +bool device_path_is_valid(const EFI_DEVICE_PATH *dp, size_t size); + EFI_DEVICE_PATH *device_path_dup(const EFI_DEVICE_PATH *dp); diff --git a/src/boot/efi-efivars.c b/src/boot/efi-efivars.c index c1ae4252c3c34..6af325966f965 100644 --- a/src/boot/efi-efivars.c +++ b/src/boot/efi-efivars.c @@ -191,6 +191,16 @@ EFI_STATUS efivar_get_raw_full( size_t size = 0; err = RT->GetVariable((char16_t *) name, (EFI_GUID *) vendor, NULL, &size, NULL); + if (err == EFI_SUCCESS) { + /* The variable exists but is empty, initialize return parameters */ + if (ret_attributes) + *ret_attributes = 0; + if (ret_data) + *ret_data = NULL; + if (ret_size) + *ret_size = 0; + return EFI_SUCCESS; + } if (err != EFI_BUFFER_TOO_SMALL) return err; @@ -222,6 +232,9 @@ EFI_STATUS efivar_get_boolean_u8(const EFI_GUID *vendor, const char16_t *name, b if (err != EFI_SUCCESS) return err; + if (size == 0) + return EFI_BUFFER_TOO_SMALL; + if (ret) *ret = *b > 0; diff --git a/src/boot/efi-string.c b/src/boot/efi-string.c index 306446386271a..efe1553696ddf 100644 --- a/src/boot/efi-string.c +++ b/src/boot/efi-string.c @@ -500,7 +500,7 @@ char* line_get_key_value(char *s, const char *sep, size_t *pos, char **ret_key, value++; /* unquote */ - if (strchr8(QUOTES, value[0]) && line[linelen - 1] == value[0]) { + if (value[0] != '\0' && strchr8(QUOTES, value[0]) && line[linelen - 1] == value[0]) { value++; line[linelen - 1] = '\0'; } diff --git a/src/boot/linux.c b/src/boot/linux.c index bd71ada48358c..67ed859993126 100644 --- a/src/boot/linux.c +++ b/src/boot/linux.c @@ -153,6 +153,25 @@ static EFI_STATUS memory_mark_rw_nx(EFI_MEMORY_ATTRIBUTE_PROTOCOL *memory_proto, return EFI_SUCCESS; } +typedef struct CleanupNxSections { + EFI_MEMORY_ATTRIBUTE_PROTOCOL *memory_proto; + struct iovec *sections; + size_t n_sections; +} CleanupNxSections; + +static void cleanup_nx_sections(CleanupNxSections *c) { + assert(c); + + /* Restore the code sections that were marked RO+X back to RW+NX before their backing pages are + * freed: EDK2 requires freed buffers to be writable and non-executable (it may overwrite them with + * a fixed pattern), otherwise FreePages() crashes. */ + if (c->memory_proto) + for (size_t i = 0; i < c->n_sections; i++) + (void) memory_mark_rw_nx(c->memory_proto, &c->sections[i]); + + free(c->sections); +} + EFI_STATUS linux_exec( EFI_HANDLE parent_image, const char16_t *cmdline, @@ -167,7 +186,7 @@ EFI_STATUS linux_exec( assert(iovec_is_set(kernel)); assert(iovec_is_valid(initrd)); - err = pe_kernel_info(kernel->iov_base, &entry_point, &compat_entry_point, &kernel_size_in_memory, §ion_alignment); + err = pe_kernel_info(kernel->iov_base, kernel->iov_len, &entry_point, &compat_entry_point, &kernel_size_in_memory, §ion_alignment); #if defined(__i386__) || defined(__x86_64__) if (err == EFI_UNSUPPORTED) /* Kernel is too old to support LINUX_INITRD_MEDIA_GUID, try the deprecated EFI handover @@ -242,8 +261,6 @@ EFI_STATUS linux_exec( * https://microsoft.github.io/mu/WhatAndWhy/enhancedmemoryprotection/ * https://www.kraxel.org/blog/2023/12/uefi-nx-linux-boot/ */ EFI_MEMORY_ATTRIBUTE_PROTOCOL *memory_proto = NULL; - _cleanup_free_ struct iovec *nx_sections = NULL; - size_t n_nx_sections = 0; if (pe_kernel_check_nx_compat(kernel->iov_base)) { /* LocateProtocol() is not quite that quick if you have many protocols, so only look for it @@ -259,8 +276,7 @@ EFI_STATUS linux_exec( const PeSectionHeader *headers; size_t n_headers; - /* Do we need to validate anything here? the len? */ - err = pe_section_table_from_base(kernel->iov_base, &headers, &n_headers); + err = pe_section_table_from_base(kernel->iov_base, kernel->iov_len, &headers, &n_headers, /* ret_size_in_memory= */ NULL); if (err != EFI_SUCCESS) return log_error_status(err, "Cannot read sections: %m"); @@ -277,6 +293,12 @@ EFI_STATUS linux_exec( /* addr= */ 0); uint8_t* loaded_kernel = PHYSICAL_ADDRESS_TO_POINTER(loaded_kernel_pages.addr); + + /* Any code section marked RO+X must be reverted to RW+NX before the backing pages are freed. */ + _cleanup_(cleanup_nx_sections) CleanupNxSections nx_restore = { + .memory_proto = memory_proto, + }; + FOREACH_ARRAY(h, headers, n_headers) { if (h->PointerToRelocations != 0) return log_error_status(EFI_LOAD_ERROR, "Inner kernel image contains sections with relocations, which we do not support."); @@ -289,6 +311,10 @@ EFI_STATUS linux_exec( return log_error_status(EFI_LOAD_ERROR, "Section would write outside of memory"); if (h->SizeOfRawData > h->VirtualSize) return log_error_status(EFI_LOAD_ERROR, "Invalid PE section, raw data size is greater than virtual size"); + if (UINT32_MAX - h->VirtualAddress < h->VirtualSize) + return log_error_status(EFI_LOAD_ERROR, "Invalid PE section, VirtualSize + VirtualAddress overflows"); + if (h->VirtualAddress + h->VirtualSize > kernel_size_in_memory) + return log_error_status(EFI_LOAD_ERROR, "Section virtual size would write outside of memory"); if (UINT32_MAX - h->PointerToRawData < h->SizeOfRawData) return log_error_status(EFI_LOAD_ERROR, "Invalid PE section, PointerToRawData + SizeOfRawData overflows"); if (h->PointerToRawData + h->SizeOfRawData > kernel->iov_len) @@ -301,15 +327,14 @@ EFI_STATUS linux_exec( /* Not a code section? Nothing to do, leave as-is. */ if (memory_proto && (h->Characteristics & (PE_CODE|PE_EXECUTE))) { - nx_sections = xrealloc(nx_sections, n_nx_sections * sizeof(struct iovec), (n_nx_sections + 1) * sizeof(struct iovec)); - nx_sections[n_nx_sections].iov_base = loaded_kernel + h->VirtualAddress; - nx_sections[n_nx_sections].iov_len = h->VirtualSize; + /* Record the section for cleanup before marking it RO+X: if memory_mark_ro_x() + * fails after partially applying the attributes, cleanup still reverts them. */ + nx_restore.sections = xrealloc(nx_restore.sections, nx_restore.n_sections * sizeof(struct iovec), (nx_restore.n_sections + 1) * sizeof(struct iovec)); + nx_restore.sections[nx_restore.n_sections++] = IOVEC_MAKE(loaded_kernel + h->VirtualAddress, h->VirtualSize); - err = memory_mark_ro_x(memory_proto, &nx_sections[n_nx_sections]); + err = memory_mark_ro_x(memory_proto, &nx_restore.sections[nx_restore.n_sections - 1]); if (err != EFI_SUCCESS) return err; - - ++n_nx_sections; } } @@ -330,8 +355,12 @@ EFI_STATUS linux_exec( _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; err = initrd_register(initrd, &initrd_handle); - if (err != EFI_SUCCESS) + if (err != EFI_SUCCESS) { + /* Restore the patched fields before kernel_file_path and loaded_kernel_pages are freed, + * otherwise the stub's own EFI_LOADED_IMAGE_PROTOCOL is left pointing at freed memory. */ + *parent_loaded_image = original_parent_loaded_image; return log_error_status(err, "Error registering initrd: %m"); + } log_wait(); @@ -349,11 +378,5 @@ EFI_STATUS linux_exec( /* Restore */ *parent_loaded_image = original_parent_loaded_image; - /* 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 - * attributes are not restored FreePages() will crash. */ - for (size_t i = 0; i < n_nx_sections; i++) - (void) memory_mark_rw_nx(memory_proto, &nx_sections[i]); - return log_error_status(err, "Error starting kernel image: %m"); } diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index ab553a6530bbc..10ba524125407 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -60,7 +60,8 @@ static bool verify_gpt(/* const */ GptHeader *h, EFI_LBA lba_expected) { if (h->MyLBA != lba_expected) return false; - if ((h->SizeOfPartitionEntry % sizeof(EFI_PARTITION_ENTRY)) != 0) + if (h->SizeOfPartitionEntry < sizeof(EFI_PARTITION_ENTRY) || + (h->SizeOfPartitionEntry % sizeof(EFI_PARTITION_ENTRY)) != 0) return false; if (h->NumberOfPartitionEntries <= 0 || h->NumberOfPartitionEntries > 1024) diff --git a/src/boot/pe.c b/src/boot/pe.c index 872cb9e6220e4..b7974b33a5089 100644 --- a/src/boot/pe.c +++ b/src/boot/pe.c @@ -140,6 +140,9 @@ typedef struct PeFileHeader { #define SECTION_TABLE_BYTES_MAX (16U * 1024U * 1024U) +/* https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-data-directories-image-only */ +#define BASE_RELOCATION_TABLE_DATA_DIRECTORY_ENTRY 5 + static bool verify_dos(const DosFileHeader *dos) { assert(dos); @@ -163,7 +166,18 @@ static bool verify_pe( (allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) && pe->FileHeader.NumberOfSections > 0 && IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC) && - pe->FileHeader.SizeOfOptionalHeader < SIZE_MAX - (dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader)); + pe->FileHeader.SizeOfOptionalHeader < SIZE_MAX - (dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader)) && + /* The optional header must be large enough to actually contain every field we read from + * it later (the deepest being the base relocation data directory entry), and must declare + * at least that many data directory entries. */ + pe->FileHeader.SizeOfOptionalHeader >= + (pe->OptionalHeader.Magic == OPTHDR32_MAGIC ? + offsetof(PeOptionalHeader, DataDirectory32) : + offsetof(PeOptionalHeader, DataDirectory64)) + + (BASE_RELOCATION_TABLE_DATA_DIRECTORY_ENTRY + 1) * sizeof(PeImageDataDirectory) && + (pe->OptionalHeader.Magic == OPTHDR32_MAGIC ? + pe->OptionalHeader.NumberOfRvaAndSizes32 : + pe->OptionalHeader.NumberOfRvaAndSizes64) > BASE_RELOCATION_TABLE_DATA_DIRECTORY_ENTRY; } static size_t section_table_offset(const DosFileHeader *dos, const PeFileHeader *pe) { @@ -173,6 +187,54 @@ static size_t section_table_offset(const DosFileHeader *dos, const PeFileHeader return dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader; } +static EFI_STATUS pe_headers_from_base( + const void *base, + size_t base_len, + bool allow_compatibility, + const DosFileHeader **ret_dos, + const PeFileHeader **ret_pe) { + + assert(base); + assert(ret_dos); + assert(ret_pe); + + /* Validate the DOS and PE headers against the buffer length */ + + if (base_len < sizeof(DosFileHeader)) + return EFI_LOAD_ERROR; + + const DosFileHeader *dos = (const DosFileHeader*) base; + if (!verify_dos(dos)) + return EFI_LOAD_ERROR; + + if (dos->ExeHeader > base_len || base_len - dos->ExeHeader < sizeof(PeFileHeader)) + return EFI_LOAD_ERROR; + + const PeFileHeader *pe = (const PeFileHeader*) ((const uint8_t*) base + dos->ExeHeader); + if (!verify_pe(dos, pe, allow_compatibility)) + return EFI_LOAD_ERROR; + + *ret_dos = dos; + *ret_pe = pe; + return EFI_SUCCESS; +} + +static bool pe_section_table_in_bounds( + const DosFileHeader *dos, + const PeFileHeader *pe, + size_t base_len) { + + assert(dos); + assert(pe); + + /* verify_pe() already bounded SizeOfOptionalHeader so this offset cannot overflow, and + * NumberOfSections is a uint16_t so the byte count cannot either. */ + size_t offset = section_table_offset(dos, pe); + size_t bytes = (size_t) pe->FileHeader.NumberOfSections * sizeof(PeSectionHeader); + + return offset <= base_len && base_len - offset >= bytes; +} + static bool pe_section_name_equal(const char *a, const char *b) { if (a == b) @@ -258,6 +320,7 @@ static void pe_locate_sections_internal( size_t n_section_table, const char *const section_names[], size_t validate_base, + size_t size_in_memory, const void *device_table, const Device *device, PeSectionVector sections[]) { @@ -288,6 +351,11 @@ static void pe_locate_sections_internal( if ((size_t) j->VirtualSize > size_max) continue; + /* The section's in-memory range must lie within the image, otherwise consumers + * reading it via memory_offset/memory_size would read past the loaded image. */ + if ((size_t) j->VirtualAddress + (size_t) j->VirtualSize > size_in_memory) + continue; + /* 2nd overflow check: ignore sections that are impossibly large also taking the * loaded base into account. */ if (validate_base != 0) { @@ -360,6 +428,7 @@ static void pe_locate_sections( size_t n_section_table, const char *const section_names[], size_t validate_base, + size_t size_in_memory, PeSectionVector sections[]) { if (!looking_for_dtbauto_or_efifw(section_names)) @@ -368,6 +437,7 @@ static void pe_locate_sections( n_section_table, section_names, validate_base, + size_in_memory, /* device_table= */ NULL, /* device= */ NULL, sections); @@ -387,6 +457,7 @@ static void pe_locate_sections( n_section_table, hwid_section_names, validate_base, + size_in_memory, /* device_table= */ NULL, /* device= */ NULL, hwids_section); @@ -403,6 +474,7 @@ static void pe_locate_sections( n_section_table, section_names, validate_base, + size_in_memory, hwids, device, sections); @@ -418,6 +490,7 @@ static void pe_locate_sections( n_section_table, section_names, validate_base, + size_in_memory, hwids, device, sections); @@ -433,12 +506,13 @@ static void pe_locate_sections( n_section_table, section_names, validate_base, + size_in_memory, hwids, device, sections); } -static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) { +static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, size_t base_len, const PeFileHeader *pe) { /* The kernel may provide alternative PE entry points for different PE architectures. This allows * booting a 64-bit kernel on 32-bit EFI that is otherwise running on a 64-bit CPU. The locations of any * such compat entry points are located in a special PE section. */ @@ -448,16 +522,29 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const static const char *const section_names[] = { ".compat", NULL }; PeSectionVector vector[1] = {}; + + /* Make sure the section table lies within the buffer before pe_locate_sections() iterates it. */ + if (!pe_section_table_in_bounds(dos, pe, base_len)) + return 0; + pe_locate_sections( (const PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)), pe->FileHeader.NumberOfSections, section_names, PTR_TO_SIZE(dos), + pe->OptionalHeader.SizeOfImage, vector); if (!PE_SECTION_VECTOR_IS_SET(vector)) /* not found */ return 0; + /* pe_locate_sections() bounded the section against SizeOfImage, the in-memory size. Here we read the + * section data straight from the file buffer 'dos', which may be smaller than SizeOfImage, so also + * require the section's data range to lie within base_len before scanning it. */ + if (vector[0].memory_offset > base_len || + vector[0].memory_size > base_len - vector[0].memory_offset) + return 0; + typedef struct { uint8_t type; uint8_t size; @@ -487,19 +574,18 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const EFI_STATUS pe_kernel_info( const void *base, + size_t base_len, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point, size_t *ret_size_in_memory, uint32_t *ret_section_alignment) { assert(base); - const DosFileHeader *dos = (const DosFileHeader *) base; - if (!verify_dos(dos)) - return EFI_LOAD_ERROR; - - const PeFileHeader *pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader); - if (!verify_pe(dos, pe, /* allow_compatibility= */ true)) - return EFI_LOAD_ERROR; + const DosFileHeader *dos; + const PeFileHeader *pe; + EFI_STATUS err = pe_headers_from_base(base, base_len, /* allow_compatibility= */ true, &dos, &pe); + if (err != EFI_SUCCESS) + return err; /* When allocating we need to also consider the virtual/uninitialized data sections, so parse it out * of the SizeOfImage field in the PE header and return it */ @@ -520,6 +606,10 @@ EFI_STATUS pe_kernel_info( return EFI_UNSUPPORTED; if (pe->FileHeader.Machine == TARGET_MACHINE_TYPE) { + /* The entry point is later called as ImageBase + entry_point, and only SizeOfImage + * bytes are allocated for the image, so reject an entry point outside of it. */ + if (pe->OptionalHeader.AddressOfEntryPoint >= size_in_memory) + return EFI_LOAD_ERROR; if (ret_entry_point) *ret_entry_point = pe->OptionalHeader.AddressOfEntryPoint; if (ret_compat_entry_point) @@ -531,10 +621,13 @@ EFI_STATUS pe_kernel_info( return EFI_SUCCESS; } - uint32_t compat_entry_point = get_compatibility_entry_address(dos, pe); + uint32_t compat_entry_point = get_compatibility_entry_address(dos, base_len, pe); if (compat_entry_point == 0) /* Image type not supported and no compat entry found. */ return EFI_UNSUPPORTED; + if (compat_entry_point >= size_in_memory) + /* Same as above: the compat entry point is called as ImageBase + entry_point. */ + return EFI_LOAD_ERROR; if (ret_entry_point) *ret_entry_point = 0; @@ -548,9 +641,6 @@ EFI_STATUS pe_kernel_info( return EFI_SUCCESS; } -/* https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-data-directories-image-only */ -#define BASE_RELOCATION_TABLE_DATA_DIRECTORY_ENTRY 5 - /* We do not expect PE inner kernels to have any relocations. However that might be wrong for some * architectures, or it might change in the future. If the case of relocation arise, we should transform this * function in a function applying the relocations. However for now, since it would not be exercised and @@ -599,20 +689,20 @@ bool pe_kernel_check_nx_compat(const void *base) { EFI_STATUS pe_section_table_from_base( const void *base, + size_t base_len, const PeSectionHeader **ret_section_table, - size_t *ret_n_section_table) { + size_t *ret_n_section_table, + size_t *ret_size_in_memory) { assert(base); assert(ret_section_table); assert(ret_n_section_table); - const DosFileHeader *dos = (const DosFileHeader*) base; - if (!verify_dos(dos)) - return EFI_LOAD_ERROR; - - const PeFileHeader *pe = (const PeFileHeader*) ((const uint8_t*) base + dos->ExeHeader); - if (!verify_pe(dos, pe, /* allow_compatibility= */ false)) - return EFI_LOAD_ERROR; + const DosFileHeader *dos; + const PeFileHeader *pe; + EFI_STATUS err = pe_headers_from_base(base, base_len, /* allow_compatibility= */ false, &dos, &pe); + if (err != EFI_SUCCESS) + return err; assert_cc(sizeof(pe->FileHeader.NumberOfSections) == sizeof(uint16_t)); /* multiplication below cannot overflow */ @@ -620,14 +710,22 @@ EFI_STATUS pe_section_table_from_base( if (n_section_table * sizeof(PeSectionHeader) > SECTION_TABLE_BYTES_MAX) return EFI_OUT_OF_RESOURCES; + /* Make sure the section table lies within the buffer, so consumers iterating it don't read off the + * end. */ + if (!pe_section_table_in_bounds(dos, pe, base_len)) + return EFI_LOAD_ERROR; + *ret_section_table = (const PeSectionHeader*) ((const uint8_t*) base + section_table_offset(dos, pe)); *ret_n_section_table = n_section_table; + if (ret_size_in_memory) + *ret_size_in_memory = pe->OptionalHeader.SizeOfImage; return EFI_SUCCESS; } EFI_STATUS pe_memory_locate_sections( const void *base, + size_t base_len, const char *const section_names[], PeSectionVector sections[]) { @@ -638,8 +736,8 @@ EFI_STATUS pe_memory_locate_sections( assert(sections); const PeSectionHeader *section_table; - size_t n_section_table; - err = pe_section_table_from_base(base, §ion_table, &n_section_table); + size_t n_section_table, size_in_memory; + err = pe_section_table_from_base(base, base_len, §ion_table, &n_section_table, &size_in_memory); if (err != EFI_SUCCESS) return err; @@ -648,6 +746,7 @@ EFI_STATUS pe_memory_locate_sections( n_section_table, section_names, PTR_TO_SIZE(base), + size_in_memory, sections); return EFI_SUCCESS; @@ -656,7 +755,8 @@ EFI_STATUS pe_memory_locate_sections( EFI_STATUS pe_section_table_from_file( EFI_FILE *handle, PeSectionHeader **ret_section_table, - size_t *ret_n_section_table) { + size_t *ret_n_section_table, + size_t *ret_size_in_memory) { EFI_STATUS err; size_t len; @@ -710,6 +810,8 @@ EFI_STATUS pe_section_table_from_file( *ret_section_table = TAKE_PTR(section_table); *ret_n_section_table = n_section_table; + if (ret_size_in_memory) + *ret_size_in_memory = pe.OptionalHeader.SizeOfImage; return EFI_SUCCESS; } @@ -777,6 +879,7 @@ EFI_STATUS pe_locate_profile_sections( const char* const section_names[], unsigned profile, size_t validate_base, + size_t size_in_memory, PeSectionVector sections[]) { assert(section_table || n_section_table == 0); @@ -798,6 +901,7 @@ EFI_STATUS pe_locate_profile_sections( n, section_names, validate_base, + size_in_memory, sections); return EFI_SUCCESS; diff --git a/src/boot/pe.h b/src/boot/pe.h index 5c8dc86fe9389..bc9963799f519 100644 --- a/src/boot/pe.h +++ b/src/boot/pe.h @@ -36,13 +36,16 @@ static inline bool PE_SECTION_VECTOR_IS_SET(const PeSectionVector *v) { EFI_STATUS pe_section_table_from_base( const void *base, + size_t base_len, const PeSectionHeader **ret_section_table, - size_t *ret_n_section_table); + size_t *ret_n_section_table, + size_t *ret_size_in_memory); EFI_STATUS pe_section_table_from_file( EFI_FILE *handle, PeSectionHeader **ret_section_table, - size_t *ret_n_section_table); + size_t *ret_n_section_table, + size_t *ret_size_in_memory); EFI_STATUS pe_locate_profile_sections( const PeSectionHeader section_table[], @@ -50,15 +53,18 @@ EFI_STATUS pe_locate_profile_sections( const char* const section_names[], unsigned profile, size_t validate_base, + size_t size_in_memory, PeSectionVector sections[]); EFI_STATUS pe_memory_locate_sections( const void *base, + size_t base_len, const char *const section_names[], PeSectionVector sections[]); EFI_STATUS pe_kernel_info( const void *base, + size_t base_len, uint32_t *ret_entry_point, uint32_t *ret_compat_entry_point, size_t *ret_size_in_memory, diff --git a/src/boot/stub.c b/src/boot/stub.c index e69faca00b0bf..94c4ac49e4bf6 100644 --- a/src/boot/stub.c +++ b/src/boot/stub.c @@ -596,7 +596,7 @@ static EFI_STATUS load_addons( if (err != EFI_SUCCESS) return log_error_status(err, "Failed to find protocol in %ls: %m", items[i]); - err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, sections); + err = pe_memory_locate_sections(loaded_addon->ImageBase, loaded_addon->ImageSize, unified_sections, sections); if (err != EFI_SUCCESS) { log_error_status(err, "Unable to locate embedded .cmdline/.dtb/.dtbauto/.efifw/.initrd/.ucode sections in %ls, ignoring: %m", @@ -1099,8 +1099,8 @@ static EFI_STATUS find_sections( assert(sections); const PeSectionHeader *section_table; - size_t n_section_table; - err = pe_section_table_from_base(loaded_image->ImageBase, §ion_table, &n_section_table); + size_t n_section_table, size_in_memory; + err = pe_section_table_from_base(loaded_image->ImageBase, loaded_image->ImageSize, §ion_table, &n_section_table, &size_in_memory); if (err != EFI_SUCCESS) return log_error_status(err, "Unable to locate PE section table: %m"); @@ -1111,6 +1111,7 @@ static EFI_STATUS find_sections( unified_sections, /* profile= */ UINT_MAX, /* validate_base= */ PTR_TO_SIZE(loaded_image->ImageBase), + size_in_memory, sections); if (err != EFI_SUCCESS) return log_error_status(err, "Unable to locate embedded base PE sections: %m"); @@ -1123,6 +1124,7 @@ static EFI_STATUS find_sections( unified_sections, profile, /* validate_base= */ PTR_TO_SIZE(loaded_image->ImageBase), + size_in_memory, sections); if (err != EFI_SUCCESS && !(err == EFI_NOT_FOUND && profile == 0)) /* the first profile is implied if it doesn't exist */ return log_error_status(err, "Unable to locate embedded per-profile PE sections: %m"); diff --git a/src/boot/test-efi-string.c b/src/boot/test-efi-string.c index 7633534dd4c07..5805cb302412d 100644 --- a/src/boot/test-efi-string.c +++ b/src/boot/test-efi-string.c @@ -590,6 +590,7 @@ TEST(line_get_key_value) { " also\tused \r\n" "for \"the conf\"\n" "format\t !!"; + char s3[] = "ID="; size_t pos = 0; char *key, *value; @@ -611,6 +612,11 @@ TEST(line_get_key_value) { ASSERT_TRUE(streq8(value, " stripping # with comments")); ASSERT_NULL(line_get_key_value(s1, "=", &pos, &key, &value)); + pos = 0; + ASSERT_NOT_NULL(line_get_key_value(s3, "=", &pos, &key, &value)); + ASSERT_TRUE(streq8(key, "ID")); + ASSERT_TRUE(streq8(value, "")); + pos = 0; ASSERT_NOT_NULL(line_get_key_value(s2, " \t", &pos, &key, &value)); ASSERT_TRUE(streq8(key, "this")); diff --git a/src/boot/vmm.c b/src/boot/vmm.c index e571a3990de64..09c121b9884f0 100644 --- a/src/boot/vmm.c +++ b/src/boot/vmm.c @@ -62,7 +62,7 @@ bool is_direct_boot(EFI_HANDLE device) { EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) { _cleanup_free_ EFI_HANDLE *handles = NULL; size_t n_handles; - EFI_STATUS err, dp_err; + EFI_STATUS err; assert(ret_vmm_dev); assert(ret_vmm_dir); @@ -79,9 +79,15 @@ EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) { for (size_t order = 0;; order++) { _cleanup_free_ EFI_DEVICE_PATH *dp = NULL; + size_t dp_size = 0; _cleanup_free_ char16_t *order_str = xasprintf("VMMBootOrder%04zx", order); - dp_err = efivar_get_raw(MAKE_GUID_PTR(VMM_BOOT_ORDER), order_str, (void**) &dp, NULL); + err = efivar_get_raw(MAKE_GUID_PTR(VMM_BOOT_ORDER), order_str, (void**) &dp, &dp_size); + + /* Drop the device path from the (untrusted) EFI variable if it doesn't validate, so the + * check below simply has to test whether it is set. */ + if (err == EFI_SUCCESS && !device_path_is_valid(dp, dp_size)) + dp = mfree(dp); for (size_t i = 0; i < n_handles; i++) { _cleanup_file_close_ EFI_FILE *root_dir = NULL, *efi_dir = NULL; @@ -92,8 +98,8 @@ EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) { if (err != EFI_SUCCESS) return err; - /* check against VMMBootOrderNNNN (if set) */ - if (dp_err == EFI_SUCCESS && !device_path_startswith(fs, dp)) + /* check against VMMBootOrderNNNN (if set and valid) */ + if (dp && !device_path_startswith(fs, dp)) continue; err = open_volume(handles[i], &root_dir); @@ -112,7 +118,7 @@ EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir) { return EFI_SUCCESS; } - if (dp_err != EFI_SUCCESS) + if (!dp) return EFI_NOT_FOUND; } assert_not_reached(); diff --git a/src/bootctl/meson.build b/src/bootctl/meson.build index 06137bdae00ce..958a804b80598 100644 --- a/src/bootctl/meson.build +++ b/src/bootctl/meson.build @@ -24,7 +24,10 @@ executables += [ ], 'sources' : bootctl_sources, 'link_with' : boot_link_with, - 'dependencies' : [libopenssl_cflags], + 'dependencies' : [ + libopenssl_cflags, + tpm2_cflags, + ], }, test_template + { 'sources' : files( diff --git a/src/clonesetup/clonesetup-generator.c b/src/clonesetup/clonesetup-generator.c new file mode 100644 index 0000000000000..fc885dd7392c5 --- /dev/null +++ b/src/clonesetup/clonesetup-generator.c @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "clonesetup-util.h" +#include "dropin.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "generator.h" +#include "log.h" +#include "path-util.h" +#include "specifier.h" +#include "string-util.h" +#include "unit-name.h" + +static const char *arg_dest = NULL; + +/* Generate unit files that call the systemd-clonesetup binary to create or remove clone devices. */ +static int generate_clone_units( + const char *clone_name, + const char *source_dev, + const char *dest_dev, + const char *metadata_dev, + const char *options) { + + /* unit files for each device */ + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(clone_name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + /* Escape clone name for unit specifiers and then ExecStart/ExecStop parsing. */ + _cleanup_free_ char *clone_name_spec_escaped = NULL; + clone_name_spec_escaped = specifier_escape(clone_name); + if (!clone_name_spec_escaped) + return log_oom(); + + /* create clone_dev_path that holds path for new cloned device */ + _cleanup_free_ char *clone_dev_path = NULL, *clone_dev_path_escaped = NULL, + *clone_dev_path_unit_escaped = NULL; + clone_dev_path = path_join("/dev/mapper", clone_name); + if (!clone_dev_path) + return log_oom(); + + clone_dev_path_escaped = specifier_escape(clone_dev_path); + if (!clone_dev_path_escaped) + return log_oom(); + + r = unit_name_path_escape(clone_dev_path, &clone_dev_path_unit_escaped); + if (r < 0) + return log_error_errno(r, "Failed to escape clone device path: %m"); + + _cleanup_free_ char *e = NULL, *unit = NULL; + /* escape clone name */ + e = unit_name_escape(clone_name); + if (!e) + return log_oom(); + + /* Generate unit name for the clone service */ + r = unit_name_build("systemd-clonesetup", e, ".service", &unit); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + /* Generate unit names for dependencies */ + _cleanup_free_ char *source_unit = NULL, *dest_unit = NULL, *metadata_unit = NULL; + r = unit_name_from_path(source_dev, ".device", &source_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate source device unit name: %m"); + + r = unit_name_from_path(dest_dev, ".device", &dest_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate dest device unit name: %m"); + + r = unit_name_from_path(metadata_dev, ".device", &metadata_unit); + if (r < 0) + return log_error_errno(r, "Failed to generate metadata device unit name: %m"); + + /* Escape device paths for unit specifiers and then ExecStart parsing. */ + _cleanup_free_ char *source_spec_escaped = NULL, + *dest_spec_escaped = NULL, *metadata_spec_escaped = NULL, + *options_spec_escaped = NULL; + source_spec_escaped = specifier_escape(source_dev); + if (!source_spec_escaped) + return log_oom(); + + dest_spec_escaped = specifier_escape(dest_dev); + if (!dest_spec_escaped) + return log_oom(); + + metadata_spec_escaped = specifier_escape(metadata_dev); + if (!metadata_spec_escaped) + return log_oom(); + + if (options) { + options_spec_escaped = specifier_escape(options); + if (!options_spec_escaped) + return log_oom(); + } + + r = generator_open_unit_file(arg_dest, /* source = */ NULL, unit, &f); + if (r < 0) + return r; + + /* With DefaultDependencies=no, order after udev so backing /dev nodes are ready in early boot. + * The : exec prefix on ExecStart=/ExecStop= disables $ { } env-var expansion by the manager. */ + fprintf(f, + "[Unit]\n" + "Description=Create dm-clone device %1$s\n" + "Documentation=man:clonetab(5) man:systemd-clonesetup(8) man:systemd-clonesetup-generator(8)\n" + "DefaultDependencies=no\n" + "BindsTo=%2$s %3$s %4$s\n" + "After=%2$s %3$s %4$s systemd-udevd-kernel.socket\n" + "Before=blockdev@%5$s.target clonesetup.target shutdown.target\n" + "Wants=blockdev@%5$s.target\n" + "Conflicts=shutdown.target\n" + "\n" + "[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "ExecStart=:" SYSTEMD_CLONESETUP_PATH " add '%6$s' '%7$s' '%8$s' '%9$s' '%10$s'\n" + "ExecStop=:" SYSTEMD_CLONESETUP_PATH " remove '%6$s'\n" + "TimeoutSec=0\n", + clone_dev_path_escaped, + source_unit, dest_unit, metadata_unit, + clone_dev_path_unit_escaped, + clone_name_spec_escaped, source_spec_escaped, dest_spec_escaped, + metadata_spec_escaped, options_spec_escaped ?: "-"); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit %s: %m", unit); + + /* symlink unit file to enable it */ + _cleanup_free_ char *dmname = NULL; + r = unit_name_from_path(clone_dev_path, ".device", &dmname); + if (r < 0) + return log_error_errno(r, "Failed to generate clone device unit name: %m"); + + r = generator_add_symlink(arg_dest, dmname, "requires", unit); + if (r < 0) + return r; + + /* Extend device timeout to allow clone service to complete */ + r = write_drop_in(arg_dest, dmname, 40, "device-timeout", + "# Automatically generated by systemd-clonesetup-generator\n\n" + "[Unit]\n" + "JobRunningTimeoutSec=infinity\n"); + if (r < 0) + log_warning_errno(r, "Failed to write device timeout drop-in: %m"); + + /* Add to clonesetup.target so it starts at boot */ + r = generator_add_symlink(arg_dest, "clonesetup.target", "requires", unit); + if (r < 0) + return r; + + return 0; +} + +/* Field validation — what each check covers: + * control chars, \, ', whitespace → string_is_safe() on all fields + * / in name → string_is_safe(name, STRING_FILENAME) + * .. in device paths → path_is_normalized() + * non-/dev/ device paths → path_is_absolute() + path_startswith(path, "/dev/") + * % specifier expansion → specifier_escape() applied before writing unit file + * $ { } env-var expansion → : exec prefix on ExecStart=/ExecStop= */ +static int validate_fields(const char *fname, unsigned clone_line, const char *name, + const char *src, const char *dst, const char *meta, const char *options) { + int r; + if (!string_is_safe(name, STRING_FILENAME)) { + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid clone name '%s' in %s:%u, ignoring.", name, fname, clone_line); + } + + r = validate_dev_path("source", src); + if (r < 0) + return r; + r = validate_dev_path("destination", dst); + if (r < 0) + return r; + r = validate_dev_path("metadata", meta); + if (r < 0) + return r; + + if (options && !string_is_safe(options, 0)) { + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid options '%s' in %s:%u, ignoring.", options, fname, clone_line); + } + return 0; +} + +static int add_clone_devices(void) { + _cleanup_fclose_ FILE *f = NULL; + unsigned clone_line = 0; + int r, ret = 0; + const char *fname; + + fname = secure_getenv("SYSTEMD_CLONETAB") ?: "/etc/clonetab"; + + r = fopen_unlocked(fname, "re", &f); + if (r < 0) { + if (r != -ENOENT) + log_error_errno(r, "Failed to open %s: %m", fname); + return 0; + } + + for (;;) { + _cleanup_free_ char *line = NULL, *src = NULL, + *name = NULL, *dst = NULL, *meta = NULL, *options = NULL; + int k; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", fname); + if (r == 0) + break; + + clone_line++; + + if (IN_SET(line[0], 0, '#')) + continue; + + k = sscanf(line, "%ms %ms %ms %ms %ms", &name, &src, &dst, &meta, &options); + if (k < 4 || k > 5) { + log_error("Failed to parse %s:%u, ignoring.", fname, clone_line); + continue; + } + + r = validate_fields(fname, clone_line, name, src, dst, meta, options); + if (r < 0) + continue; + RET_GATHER(ret, generate_clone_units(name, src, dst, meta, options)); + } + + return ret; +} + +/* This generator reads /etc/clonetab and for each entry, writes unit files + * (creates systemd-clonesetup@.service and clonesetup.target.requires/systemd-clonesetup@.service) + * that clonesetup.target requires, and that run systemd-clonesetup (add device at boot, + * remove it at shutdown); systemd-clonesetup (used in systemd-clonesetup@.service) is the binary that + * uses device-mapper ioctls to create and remove the dm-clone devices. + * clonesetup.target groups these units so they run together at boot. + * Boot chain: sysinit.target has clonesetup.target in sysinit.target.wants/ (see units/meson.build), + * so at boot clonesetup.target starts and pulls in these units via clonesetup.target.requires/. */ +static int run(const char *dest, const char *dest_early, const char *dest_late) { + + /* dest usually is /run/systemd/generator */ + assert_se(arg_dest = dest); + + return add_clone_devices(); +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/clonesetup/clonesetup-ioctl.c b/src/clonesetup/clonesetup-ioctl.c new file mode 100644 index 0000000000000..73c52e6e99770 --- /dev/null +++ b/src/clonesetup/clonesetup-ioctl.c @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "sd-device.h" + +#include "clonesetup-ioctl.h" +#include "device-private.h" +#include "errno-util.h" +#include "fd-util.h" +#include "log.h" +#include "memory-util.h" +#include "string-util.h" + +/* Returns the size in bytes of the block device at dev_path. + * Loading the dm-clone table needs the source device size in sectors; sysfs + * reports size in 512-byte sectors. This reads sysfs and returns bytes so the + * caller can divide by 512 and pass the sector count to dm_clone_load_table(). */ +static int get_size(const char *dev_path, uint64_t *ret_size) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + uint64_t size, temp_size; + int r; + + assert(dev_path); + assert(ret_size); + + r = sd_device_new_from_devname(&dev, dev_path); + if (r < 0) + return log_error_errno(r, "Failed to create device from '%s': %m", dev_path); + + r = device_get_sysattr_u64(dev, "size", &size); + if (r < 0) + return log_error_errno(r, "Failed to get device size for '%s': %m", dev_path); + + /* sysfs 'size' is in 512-byte sectors */ + temp_size = u64_multiply_safe(size, 512); + if (temp_size == 0 && size != 0) + return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), + "Device size overflow for '%s'", dev_path); + + *ret_size = temp_size; + return 0; +} + +/* Common helper used to run dm ioctls. */ +static int dm_ioctl_run(const char *name, uint32_t cmd, struct dm_ioctl *data, size_t data_size) { + _cleanup_close_ int fd = -EBADF; + struct dm_ioctl *dm = data; + int r; + + assert(name); + assert(data); + assert(data_size >= sizeof(struct dm_ioctl)); + + dm->version[0] = DM_VERSION_MAJOR; + dm->version[1] = DM_VERSION_MINOR; + dm->version[2] = DM_VERSION_PATCHLEVEL; + dm->data_size = data_size; + + if (strlen(name) >= sizeof_field(struct dm_ioctl, name)) + return log_error_errno(SYNTHETIC_ERRNO(ENAMETOOLONG), + "DM device name too long: %s", name); + strncpy_exact(dm->name, name, sizeof(dm->name)); + + fd = open("/dev/mapper/control", O_RDWR | O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open /dev/mapper/control: %m"); + + r = RET_NERRNO(ioctl(fd, cmd, dm)); + if (r < 0) { + if (r == -ENXIO && cmd == DM_DEV_REMOVE) { + log_full_errno(LOG_DEBUG, r, "Device \"%s\" is already inactive, ignoring: %m", dm->name); + return 0; + } + return log_error_errno(r, "DM ioctl failed: %m"); + } + return 0; +} + +/* First dm ioctl needed to create a device. */ +static int dm_clone_create(const char *name) { + int r; + assert(name); + + struct dm_ioctl dm = {}; + r = dm_ioctl_run(name, DM_DEV_CREATE, &dm, sizeof(dm)); + if (r < 0) { + if (r == -EEXIST) + return log_error_errno(r, "Device '/dev/mapper/%s' already exists.", name); + return log_error_errno(r, "Failed to create DM device '%s': %m", name); + } + return 0; +} + +/* Second dm ioctl needed to create a device. */ +static int dm_clone_load_table(const char *name, uint64_t size_sectors, const char *target_params) { + char *params_buf; + size_t params_len, dm_size; + _cleanup_free_ struct dm_ioctl *dm = NULL; + struct dm_target_spec *tgt; + + assert(name); + assert(target_params); + + params_len = strlen(target_params) + 1; + /* ensure that dm_size is always aligned, so it makes the buffer actually match what .next claims */ + dm_size = ALIGN8(sizeof(struct dm_ioctl)) + ALIGN8(sizeof(struct dm_target_spec) + params_len); + dm = malloc0(dm_size); + if (!dm) + return log_oom(); + *dm = (struct dm_ioctl) { + .data_start = ALIGN8(sizeof(struct dm_ioctl)), + .target_count = 1, + }; + + tgt = CAST_ALIGN_PTR(struct dm_target_spec, (uint8_t *) dm + dm->data_start); + *tgt = (struct dm_target_spec) { + .length = size_sectors, + /* Per linux/dm-ioctl.h: next is the byte offset from this dm_target_spec to the next one, + * rounded up to 8-byte alignment. With target_count == 1 next == 0 works, but set it + * correctly to avoid silent breakage if a second target is ever added. */ + .next = ALIGN8(sizeof(struct dm_target_spec) + params_len), + }; + strncpy(tgt->target_type, "clone", sizeof(tgt->target_type)); + + params_buf = (char *) tgt + ALIGN8(sizeof(struct dm_target_spec)); + memcpy(params_buf, target_params, params_len); + + return dm_ioctl_run(name, DM_TABLE_LOAD, dm, dm_size); +} + +/* Third and final dm ioctl needed to create a device. */ +static int dm_clone_activate(const char *name) { + assert(name); + + struct dm_ioctl dm = {}; + + return dm_ioctl_run(name, DM_DEV_SUSPEND, &dm, sizeof(dm)); +} + +/* Calls multiple dm ioctls to create device. */ +int dm_clone_create_device( + const char *name, + const char *source_dev, + const char *dest_dev, + const char *metadata_dev, + uint64_t region_size_bytes) { + + _cleanup_free_ char *target_params = NULL; + uint64_t src_dev_size_sectors, src_dev_size, region_size_sectors; + int r; + + assert(name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + r = get_size(source_dev, &src_dev_size); + if (r < 0) + return r; + + /* The device mapper kernel API always uses 512-byte sectors, regardless of the + * physical block size of the device (all DM targets use sector_t which is 512B). + * + * get_size internally uses sysfs i.e. /sys/block//size which also reports device size in + * 512-byte sectors. Before returning, get_size multiplies the size returned by sysfs to bytes. So we + * divide the received byte size by 512 to get the sector count for the DM table. */ + assert(src_dev_size % 512 == 0); + src_dev_size_sectors = src_dev_size / 512; + + assert(region_size_bytes % 512 == 0); + region_size_sectors = region_size_bytes / 512; + + /* dm-clone target params: [options] + * region_size = region size in sectors, configurable via clonetab (default 8 = 4KB regions with + * 512-byte sectors) 1 = hydration threshold (regions to hydrate per batch) no_hydration = don't + * start automatic background hydration + * + * The DM table "target_params" string is passed directly to the kernel via ioctl(fd, DM_TABLE_LOAD, + * ...) in dm_clone_load_table as a raw byte buffer. The kernel's DM table parser + * (drivers/md/dm-table.c) simply splits the params string on whitespace, so the only constraint is + * that the paths in params - metadata_dev, dest_dev, source_dev, and region_size must not contain + * spaces, which standard /dev/ paths never do, so the below args do NOT require shell escaping */ + if (asprintf(&target_params, "%s %s %s %" PRIu64 " 1 no_hydration", + metadata_dev, dest_dev, source_dev, region_size_sectors) < 0) + return log_oom(); + + r = dm_clone_create(name); + if (r < 0) + return r; + + r = dm_clone_load_table(name, src_dev_size_sectors, target_params); + if (r < 0) + goto fail; + + r = dm_clone_activate(name); + if (r < 0) + goto fail; + + log_info("Device %s active.", name); + return 0; + +fail: + (void) dm_clone_remove_device_deferred(name); + return r; +} + +/* Calls dm ioctl to send a message to the device. dm_ioctl is the kernel's generic device mapper envelope — + * every ioctl needs it. dm_target_msg is specific to the "send a message" operation + * */ +int dm_clone_send_message(const char *name, const char *message) { + _cleanup_free_ struct dm_ioctl *dm = NULL; + struct dm_target_msg *msg; + size_t dm_size, msg_len; + + assert(name); + assert(message); + + msg_len = strlen(message) + 1; + /* need to take into account both headers in size calculation */ + dm_size = ALIGN8(sizeof(struct dm_ioctl)) + sizeof(struct dm_target_msg) + msg_len; + dm = malloc0(dm_size); + if (!dm) + return log_oom(); + *dm = (struct dm_ioctl) { + /* with ALIGN8 call below, dm_target_msg starts at ALIGN8(sizeof(struct dm_ioctl)) which is + * already aligned, so the dm_target_msg struct lands correctly */ + .data_start = ALIGN8(sizeof(struct dm_ioctl)), + }; + + msg = CAST_ALIGN_PTR(struct dm_target_msg, (uint8_t *) dm + dm->data_start); + memcpy(msg->message, message, msg_len); + + return dm_ioctl_run(name, DM_TARGET_MSG, dm, dm_size); +} + +/* Calls dm ioctl to remove a device. flags if set can be used + * for deferred remove - e.g. DM_DEFERRED_REMOVE */ +static int dm_clone_remove_device_full(const char *name, uint32_t flags) { + struct dm_ioctl dm = { + .flags = flags, + }; + + assert(name); + + return dm_ioctl_run(name, DM_DEV_REMOVE, &dm, sizeof(dm)); +} + +/* Calls dm ioctl to remove a device. */ +int dm_clone_remove_device(const char *name) { + int r; + + assert(name); + r = dm_clone_remove_device_full(name, 0); + if (r < 0) + return r; + + log_info("Device %s inactive.", name); + return 0; +} + +/* Calls dm ioctl for deferred removal i.e. DM_DEFERRED_REMOVE */ +int dm_clone_remove_device_deferred(const char *name) { + int r; + + assert(name); + r = dm_clone_remove_device_full(name, DM_DEFERRED_REMOVE); + if (r < 0) + return r; + + log_info("Device %s marked for deferred removal.", name); + return 0; +} diff --git a/src/clonesetup/clonesetup-ioctl.h b/src/clonesetup/clonesetup-ioctl.h new file mode 100644 index 0000000000000..ba3762eb3ef1b --- /dev/null +++ b/src/clonesetup/clonesetup-ioctl.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +int dm_clone_create_device( + const char *name, + const char *source_dev, + const char *dest_dev, + const char *metadata_dev, + uint64_t region_size); + +int dm_clone_send_message(const char *name, const char *message); + +int dm_clone_remove_device(const char *name); +int dm_clone_remove_device_deferred(const char *name); diff --git a/src/clonesetup/clonesetup-util.c b/src/clonesetup/clonesetup-util.c new file mode 100644 index 0000000000000..299408a9cef18 --- /dev/null +++ b/src/clonesetup/clonesetup-util.c @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "clonesetup-util.h" +#include "errno-util.h" +#include "log.h" +#include "path-util.h" +#include "string-util.h" + +int validate_dev_path(const char *what, const char *path) { + if (!string_is_safe(path, 0) || !path_is_normalized(path) || + !path_is_absolute(path) || !path_startswith(path, "/dev/")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid %s device path '%s'.", what, path); + return 0; +} diff --git a/src/clonesetup/clonesetup-util.h b/src/clonesetup/clonesetup-util.h new file mode 100644 index 0000000000000..2c2fbf06d72a9 --- /dev/null +++ b/src/clonesetup/clonesetup-util.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int validate_dev_path(const char *what, const char *path); diff --git a/src/clonesetup/clonesetup.c b/src/clonesetup/clonesetup.c new file mode 100644 index 0000000000000..413a6cc34d24c --- /dev/null +++ b/src/clonesetup/clonesetup.c @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + +#include "alloc-util.h" +#include "build.h" +#include "clonesetup-ioctl.h" +#include "clonesetup-util.h" +#include "extract-word.h" +#include "format-table.h" +#include "help-util.h" +#include "log.h" +#include "main-func.h" +#include "options.h" +#include "parse-util.h" +#include "path-util.h" /* filename_is_valid */ +#include "string-util.h" +#include "strv.h" /* strv_skip */ +#include "verbs.h" + +/* region_size: size of each dm-clone region in bytes. Handled internally in bytes, but must correspond to a + * power of 2 between 4K and 1G per dm-clone kernel docs. */ +#define CLONE_REGION_SIZE_DEFAULT_BYTES (UINT64_C(1) << 12) /* 4 KiB */ +#define CLONE_REGION_SIZE_MIN_BYTES (UINT64_C(1) << 12) /* 4 KiB */ +#define CLONE_REGION_SIZE_MAX_BYTES (UINT64_C(1) << 30) /* 1 GiB */ + +static int parse_clone_options(const char *options, uint64_t *ret_region_size_bytes) { + uint64_t region_size_bytes = CLONE_REGION_SIZE_DEFAULT_BYTES; + + assert(ret_region_size_bytes); + + for (;;) { + _cleanup_free_ char *word = NULL; + const char *val; + int r; + + /* extract_first_word: * + * Returns > 0 — successfully extracted a word * + * Returns 0 — no more words (end of string) * + * Returns < 0 — actual error (e.g. memory allocation failure) */ + r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to parse clone options: %m"); + if (r == 0) + break; + + /* treat - as empty — common placeholders for "no options" */ + if (streq(word, "-")) + continue; + + if ((val = startswith(word, "region_size="))) { + uint64_t r_size_bytes; + /* parse_size handles suffixes like K, M, G automatically */ + r = parse_size(val, 1024, &r_size_bytes); + if (r < 0) + log_warning_errno(r, "Failed to parse region_size= value '%s', using default.", val); + else if (!ISPOWEROF2(r_size_bytes) || r_size_bytes < CLONE_REGION_SIZE_MIN_BYTES || r_size_bytes > CLONE_REGION_SIZE_MAX_BYTES) + log_warning("region_size=%s must be a power of two between 4K and 1G, using default.", val); + else + region_size_bytes = r_size_bytes; + } else { + /* currently only region_size is supported */ + log_warning("Unknown clone option '%s', ignoring.", word); + } + } + *ret_region_size_bytes = region_size_bytes; + return 0; +} + +/* dm-clone device creation workflow: + * 1. Create the dm-clone device + * 2. Enable background hydration */ +static int clone_device( + const char *clone_name, + const char *source_dev, + const char *dest_dev, + const char *metadata_dev, + const char *options) { + + int r; + + assert(clone_name); + assert(source_dev); + assert(dest_dev); + assert(metadata_dev); + + uint64_t region_size_bytes; + r = parse_clone_options(options, ®ion_size_bytes); + if (r < 0) + return r; + + r = dm_clone_create_device(clone_name, source_dev, dest_dev, metadata_dev, region_size_bytes); + if (r < 0) + return r; + + r = dm_clone_send_message(clone_name, "enable_hydration"); + if (r < 0) { + (void) dm_clone_remove_device_deferred(clone_name); + return r; + } + return 0; +} + +static int validate_fields(const char *name, const char *src, const char *dst, + const char *meta, const char *options) { + if (!filename_is_valid(name)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid clone name '%s'.", name); + + int r; + r = validate_dev_path("source", src); + if (r < 0) + return r; + r = validate_dev_path("destination", dst); + if (r < 0) + return r; + r = validate_dev_path("metadata", meta); + if (r < 0) + return r; + if (!string_is_safe(options, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid options '%s'.", options); + return 0; +} + +VERB(verb_add, "add", "NAME SOURCE DEST METADATA [OPTIONS]", 5, 6, 0, "Create a dm-clone device"); + +/* Arguments: systemd-clonesetup add NAME SOURCE DEST METADATA [OPTIONS] */ +static int verb_add(int argc, char *argv[], uintptr_t data, void *userdata) { + int r; + assert(argc >= 5 && argc <= 6); + + const char *name = ASSERT_PTR(argv[1]); + const char *src = ASSERT_PTR(argv[2]); + const char *dst = ASSERT_PTR(argv[3]); + const char *meta = ASSERT_PTR(argv[4]); + + const char *options = argc == 6 ? argv[5] : ""; + + r = validate_fields(name, src, dst, meta, options); + if (r < 0) + return r; + + log_debug("%s %s %s %s %s opts=%s", __func__, + name, src, dst, meta, options); + return clone_device(name, src, dst, meta, options); +} + +VERB(verb_remove, "remove", "NAME", 2, 2, 0, "Remove a dm-clone device"); + +static int verb_remove(int argc, char *argv[], uintptr_t data, void *userdata) { + const char *name = ASSERT_PTR(argv[1]); + int r; + + r = dm_clone_remove_device(name); + if (r == -EBUSY) { + return dm_clone_remove_device_deferred(name); + } + if (r < 0) + return r; + + return 0; +} + +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + help_cmdline("add NAME SOURCE DEST METADATA [OPTIONS]"); + help_cmdline("remove NAME"); + help_abstract("Add or remove a dm-clone device."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-clonesetup", "8"); + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + } + + return 1; +} + +/* systemd-clonesetup uses device-mapper ioctls to create and remove the + * dm-clone devices. */ +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + umask(0022); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/clonesetup/meson.build b/src/clonesetup/meson.build new file mode 100644 index 0000000000000..1793d2c71669f --- /dev/null +++ b/src/clonesetup/meson.build @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +systemd_clonesetup_sources = files( + 'clonesetup-ioctl.c', + 'clonesetup.c', +) + +executables += [ + libexec_template + { + 'name' : 'systemd-clonesetup', + 'sources' : systemd_clonesetup_sources, + 'extract' : files('clonesetup-util.c'), + }, + generator_template + { + 'name' : 'systemd-clonesetup-generator', + 'sources' : files('clonesetup-generator.c'), + 'objects' : ['systemd-clonesetup'], + }, +] diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index c9e270a6682fa..bf17e2df46f01 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -2082,6 +2082,14 @@ int config_parse_timer( log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse calendar specification, ignoring: %s", k); return 0; } + + int wday; + if (calendar_spec_weekday_conflicts(c, &wday)) + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Weekday constraint does not match the fixed date %04d-%02d-%02d " + "(which is a %s), so this timer will never elapse.", + c->year->start, c->month->start, c->day->start, + weekday_to_string(wday)); } else { r = parse_sec(k, &usec); if (r < 0) { diff --git a/src/core/meson.build b/src/core/meson.build index 26c84fa78b2f2..e46698b4cf8ec 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -174,14 +174,18 @@ libcore_static = static_library( include_directories : core_includes, implicit_include_directories : false, c_args : ['-fvisibility=default'], - dependencies : [libaudit_cflags, - libbpf_cflags, - libcryptsetup_cflags, - libm, - libmount_cflags, - libseccomp_cflags, - libselinux_cflags, - userspace], + dependencies : [ + libacl_cflags, + libaudit_cflags, + libbpf_cflags, + libcryptsetup_cflags, + libm, + libmount_cflags, + libpcre2_cflags, + libseccomp_cflags, + libselinux_cflags, + userspace, + ], build_by_default : false) libcore = shared_library( @@ -210,6 +214,7 @@ core_libs_shared = [ systemd_deps = [ libapparmor_cflags, + libaudit_cflags, libkmod_cflags, libmount_cflags, libseccomp_cflags, @@ -226,6 +231,7 @@ if conf.get('SYSTEMD_MULTICALL_BINARY') == 1 systemd_deps += [ libbpf_cflags, libcryptsetup_cflags, + libopenssl_cflags, libpam_cflags, ] endif @@ -282,6 +288,7 @@ else libbpf_cflags, libcryptsetup_cflags, libmount_cflags, + libopenssl_cflags, libpam_cflags, libseccomp_cflags, libselinux_cflags, diff --git a/src/core/service.c b/src/core/service.c index 9e4af299f7ec9..012336b47a0ba 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include /* IWYU pragma: keep */ #include #include #include @@ -36,6 +35,7 @@ #include "glyph-util.h" #include "id128-util.h" #include "image-policy.h" +#include "libaudit-util.h" /* IWYU pragma: keep */ #include "log.h" #include "luo-util.h" #include "manager.h" diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index 0e6bfa961890d..5232da633c0cd 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -98,57 +98,231 @@ static int version_build_json(const MetricFamily *mf, sd_varlink *vl, void *user /* fields= */ NULL); } -static int boot_timestamp_build_json( - const MetricFamily *mf, - sd_varlink *vl, - const dual_timestamp *t, - bool with_monotonic) { +/* Single source of truth for all manager timestamp metrics, driving both List (the values) and Describe + * (the schema advertised below). The .description is prefixed with "CLOCK_REALTIME "/"CLOCK_MONOTONIC " on + * emission; firmware, loader and kernel have no meaningful monotonic value (a pre-kernel offset or zero), + * hence they only expose the .Realtime metric (with_monotonic=false). */ +static const struct { + const char *name; + bool with_monotonic; + const char *description; +} manager_timestamp_metrics[_MANAGER_TIMESTAMP_MAX] = { + [MANAGER_TIMESTAMP_FIRMWARE] = { + .name = "FirmwareTimestamp", + .with_monotonic = false, + .description = "microseconds at which the firmware began execution (CLOCK_MONOTONIC is a pre-kernel offset, not reported)", + }, + [MANAGER_TIMESTAMP_LOADER] = { + .name = "LoaderTimestamp", + .with_monotonic = false, + .description = "microseconds at which the boot loader began execution (CLOCK_MONOTONIC is a pre-kernel offset, not reported)", + }, + [MANAGER_TIMESTAMP_KERNEL] = { + .name = "KernelTimestamp", + .with_monotonic = false, + .description = "microseconds at which the kernel started (CLOCK_MONOTONIC == 0)", + }, + [MANAGER_TIMESTAMP_INITRD] = { + .name = "InitRDTimestamp", + .with_monotonic = true, + .description = "microseconds at which the initrd began execution", + }, + [MANAGER_TIMESTAMP_USERSPACE] = { + .name = "UserspaceTimestamp", + .with_monotonic = true, + .description = "microseconds at which userspace was reached", + }, + [MANAGER_TIMESTAMP_FINISH] = { + .name = "FinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which userspace finished booting", + }, + [MANAGER_TIMESTAMP_SECURITY_START] = { + .name = "SecurityStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager started uploading security policies to the kernel", + }, + [MANAGER_TIMESTAMP_SECURITY_FINISH] = { + .name = "SecurityFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager finished uploading security policies to the kernel", + }, + [MANAGER_TIMESTAMP_GENERATORS_START] = { + .name = "GeneratorsStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager started executing generators", + }, + [MANAGER_TIMESTAMP_GENERATORS_FINISH] = { + .name = "GeneratorsFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager finished executing generators", + }, + [MANAGER_TIMESTAMP_UNITS_LOAD_START] = { + .name = "UnitsLoadStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager first started loading units", + }, + [MANAGER_TIMESTAMP_UNITS_LOAD_FINISH] = { + .name = "UnitsLoadFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager first finished loading units", + }, + [MANAGER_TIMESTAMP_UNITS_LOAD] = { + .name = "UnitsLoadTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager last started loading units", + }, + [MANAGER_TIMESTAMP_INITRD_SECURITY_START] = { + .name = "InitRDSecurityStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager started uploading security policies to the kernel in the initrd", + }, + [MANAGER_TIMESTAMP_INITRD_SECURITY_FINISH] = { + .name = "InitRDSecurityFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager finished uploading security policies to the kernel in the initrd", + }, + [MANAGER_TIMESTAMP_INITRD_GENERATORS_START] = { + .name = "InitRDGeneratorsStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager started executing generators in the initrd", + }, + [MANAGER_TIMESTAMP_INITRD_GENERATORS_FINISH] = { + .name = "InitRDGeneratorsFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager finished executing generators in the initrd", + }, + [MANAGER_TIMESTAMP_INITRD_UNITS_LOAD_START] = { + .name = "InitRDUnitsLoadStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager first started loading units in the initrd", + }, + [MANAGER_TIMESTAMP_INITRD_UNITS_LOAD_FINISH] = { + .name = "InitRDUnitsLoadFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which the manager first finished loading units in the initrd", + }, + [MANAGER_TIMESTAMP_SHUTDOWN_START] = { + .name = "ShutdownStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which shutdown began, i.e. units started to be stopped", + }, + [MANAGER_TIMESTAMP_SHUTDOWN_FINISH] = { + .name = "ShutdownFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which all units finished stopping during shutdown", + }, + [MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_START] = { + .name = "PreviousShutdownStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which shutdown began during the previous boot, i.e. units started to be stopped, if available (e.g.: kexec or soft-reboot)", + }, + [MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_FINISH] = { + .name = "PreviousShutdownFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which all units finished stopping during the shutdown of the previous boot, if available (e.g.: kexec or soft-reboot)", + }, + [MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_LATE_START] = { + .name = "PreviousShutdownLateStartTimestamp", + .with_monotonic = true, + .description = "microseconds at which systemd-shutdown began execution during the previous boot, restored from the LUO payload after a kexec-based live update", + }, + [MANAGER_TIMESTAMP_PREVIOUS_SHUTDOWN_LATE_FINISH] = { + .name = "PreviousShutdownLateFinishTimestamp", + .with_monotonic = true, + .description = "microseconds at which systemd-shutdown was about to kexec into the current kernel during the previous boot, restored from the LUO payload after a kexec-based live update", + }, +}; +static int manager_timestamps_build_json(sd_varlink *vl, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); int r; - assert(mf && mf->name); assert(vl); - assert(t); - if (timestamp_is_set(t->realtime)) { - r = metric_build_send_unsigned( - mf, /* the .Realtime metric family entry */ - vl, - /* object= */ NULL, - t->realtime, - /* fields= */ NULL); - if (r < 0) - return r; - } + FOREACH_ELEMENT(i, manager_timestamp_metrics) { + if (!i->name) + continue; - if (with_monotonic && timestamp_is_set(t->monotonic)) { - assert(endswith(mf[1].name, ".Monotonic")); - r = metric_build_send_unsigned( - mf + 1, /* the .Monotonic sibling is the next entry */ - vl, - /* object= */ NULL, - t->monotonic, - /* fields= */ NULL); - if (r < 0) - return r; + const dual_timestamp *t = manager->timestamps + (i - manager_timestamp_metrics); + + if (timestamp_is_set(t->realtime)) { + _cleanup_free_ char *name = strjoin(METRIC_IO_SYSTEMD_MANAGER_PREFIX, i->name, ".Realtime"); + if (!name) + return -ENOMEM; + + r = metric_build_send_unsigned( + &(const MetricFamily) { .name = name }, + vl, + /* object= */ NULL, + t->realtime, + /* fields= */ NULL); + if (r < 0) + return r; + } + + if (i->with_monotonic && timestamp_is_set(t->monotonic)) { + _cleanup_free_ char *name = strjoin(METRIC_IO_SYSTEMD_MANAGER_PREFIX, i->name, ".Monotonic"); + if (!name) + return -ENOMEM; + + r = metric_build_send_unsigned( + &(const MetricFamily) { .name = name }, + vl, + /* object= */ NULL, + t->monotonic, + /* fields= */ NULL); + if (r < 0) + return r; + } } return 0; } -static int kernel_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_KERNEL], /* with_monotonic= */ false); -} +static int manager_timestamps_describe(sd_varlink *link) { + int r; -static int userspace_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_USERSPACE], /* with_monotonic= */ true); -} + assert(link); -static int finish_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - return boot_timestamp_build_json(mf, vl, &manager->timestamps[MANAGER_TIMESTAMP_FINISH], /* with_monotonic= */ true); + FOREACH_ELEMENT(i, manager_timestamp_metrics) { + if (!i->name) + continue; + + _cleanup_free_ char *rt_name = strjoin(METRIC_IO_SYSTEMD_MANAGER_PREFIX, i->name, ".Realtime"); + _cleanup_free_ char *rt_description = strjoin("CLOCK_REALTIME ", i->description); + if (!rt_name || !rt_description) + return -ENOMEM; + + r = metric_family_describe( + &(const MetricFamily) { + .name = rt_name, + .description = rt_description, + .type = METRIC_FAMILY_TYPE_GAUGE, + }, + link); + if (r < 0) + return r; + + if (i->with_monotonic) { + _cleanup_free_ char *mt_name = strjoin(METRIC_IO_SYSTEMD_MANAGER_PREFIX, i->name, ".Monotonic"); + _cleanup_free_ char *mt_description = strjoin("CLOCK_MONOTONIC ", i->description); + if (!mt_name || !mt_description) + return -ENOMEM; + + r = metric_family_describe( + &(const MetricFamily) { + .name = mt_name, + .description = mt_description, + .type = METRIC_FAMILY_TYPE_GAUGE, + }, + link); + if (r < 0) + return r; + } + } + + return 0; } static int state_change_timestamp_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) { @@ -457,19 +631,6 @@ static const MetricFamily metric_family_table[] = { .type = METRIC_FAMILY_TYPE_GAUGE, .generate = active_timestamp_build_json, }, - { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "FinishTimestamp.Realtime", - .description = "CLOCK_REALTIME microseconds at which userspace finished booting", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = finish_timestamp_build_json, - }, - { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "FinishTimestamp.Monotonic", - .description = "CLOCK_MONOTONIC microseconds at which userspace finished booting", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = NULL, - }, - /* Keep those ↑ in sync with finish_timestamp_build_json(). */ { .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "InactiveExitTimestamp", .description = "Per unit metric: timestamp when the unit last exited the inactive state in microseconds; 0 indicates the transition has not occurred", @@ -482,12 +643,6 @@ static const MetricFamily metric_family_table[] = { .type = METRIC_FAMILY_TYPE_GAUGE, .generate = jobs_queued_build_json, }, - { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "KernelTimestamp.Realtime", - .description = "CLOCK_REALTIME microseconds at which the kernel started (CLOCK_MONOTONIC == 0)", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = kernel_timestamp_build_json, - }, { .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "NRestarts", .description = "Per unit metric: number of restarts", @@ -554,19 +709,6 @@ static const MetricFamily metric_family_table[] = { .type = METRIC_FAMILY_TYPE_GAUGE, .generate = units_total_build_json, }, - { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UserspaceTimestamp.Realtime", - .description = "CLOCK_REALTIME microseconds at which userspace was reached", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = userspace_timestamp_build_json, - }, - { - .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "UserspaceTimestamp.Monotonic", - .description = "CLOCK_MONOTONIC microseconds at which userspace was reached", - .type = METRIC_FAMILY_TYPE_GAUGE, - .generate = NULL, - }, - /* Keep those ↑ in sync with userspace_timestamp_build_json(). */ { .name = METRIC_IO_SYSTEMD_MANAGER_PREFIX "Version", .description = "Version of systemd", @@ -577,9 +719,21 @@ static const MetricFamily metric_family_table[] = { }; int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - return metrics_method_describe(metric_family_table, link, parameters, flags, userdata); + int r; + + r = metrics_method_describe(metric_family_table, link, parameters, flags, userdata); + if (r < 0) + return r; + + return manager_timestamps_describe(link); } int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - return metrics_method_list(metric_family_table, link, parameters, flags, userdata); + int r; + + r = metrics_method_list(metric_family_table, link, parameters, flags, userdata); + if (r < 0) + return r; + + return manager_timestamps_build_json(link, userdata); } diff --git a/src/coredump/meson.build b/src/coredump/meson.build index b0753d86fa882..b1286f80ff679 100644 --- a/src/coredump/meson.build +++ b/src/coredump/meson.build @@ -38,7 +38,10 @@ executables += [ 'sources' : systemd_coredump_sources, 'extract' : systemd_coredump_extract_sources, 'link_with' : [libshared], - 'dependencies' : common_dependencies, + 'dependencies' : [ + common_dependencies, + libacl_cflags, + ], }, executable_template + { 'name' : 'coredumpctl', diff --git a/src/creds/meson.build b/src/creds/meson.build index c18fe2ec8901d..6a14f8e0750bf 100644 --- a/src/creds/meson.build +++ b/src/creds/meson.build @@ -12,6 +12,7 @@ executables += [ 'dependencies' : [ libmount_cflags, libopenssl_cflags, + tpm2_cflags, ], }, ] diff --git a/src/cryptenroll/meson.build b/src/cryptenroll/meson.build index 3fbb5bf080bcc..f0d08ea56c2fd 100644 --- a/src/cryptenroll/meson.build +++ b/src/cryptenroll/meson.build @@ -27,6 +27,7 @@ executables += [ libfido2_cflags, libopenssl_cflags, libp11kit_cflags, + tpm2_cflags, ], }, ] diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index 0bfe0c2ec7797..4cf6b7654c22a 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c index 63be8a7c64a76..6952bb7ca2393 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-json.h" diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 552d81da1702d..450399ec129ab 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c index c6cfdcf6efeb8..eeb5ca0242308 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "cryptsetup-token-util.h" #include "hexdecoct.h" diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build index 772c29f50f526..078062d073f1c 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -5,7 +5,10 @@ lib_cryptsetup_token_common = static_library( 'cryptsetup-token-util.c', include_directories : includes, implicit_include_directories : false, - dependencies : userspace, + dependencies : [ + libcryptsetup_cflags, + userspace, + ], link_with : libshared, build_by_default : false) diff --git a/src/cryptsetup/meson.build b/src/cryptsetup/meson.build index 9b7f3fa344da5..85f15885c021f 100644 --- a/src/cryptsetup/meson.build +++ b/src/cryptsetup/meson.build @@ -23,11 +23,15 @@ executables += [ libmount_cflags, libopenssl_cflags, libp11kit_cflags, + tpm2_cflags, ], }, generator_template + { 'name' : 'systemd-cryptsetup-generator', 'sources' : files('cryptsetup-generator.c'), + 'dependencies' : [ + libcryptsetup_cflags, + ], }, ] diff --git a/src/dissect/meson.build b/src/dissect/meson.build index 24943cc802905..92e23058979b5 100644 --- a/src/dissect/meson.build +++ b/src/dissect/meson.build @@ -9,6 +9,9 @@ executables += [ 'name' : 'systemd-dissect', 'public' : true, 'sources' : files('dissect.c'), + 'dependencies' : [ + libarchive_cflags, + ], }, ] diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build index 65fc6896f1f77..10848a77e6fc7 100644 --- a/src/fuzz/meson.build +++ b/src/fuzz/meson.build @@ -8,7 +8,6 @@ simple_fuzzers += files( 'fuzz-env-file.c', 'fuzz-hostname-setup.c', 'fuzz-json.c', - 'fuzz-pe-binary.c', 'fuzz-time-util.c', 'fuzz-udev-database.c', 'fuzz-user-record.c', @@ -16,6 +15,13 @@ simple_fuzzers += files( 'fuzz-varlink-idl.c', ) +executables += [ + fuzz_template + { + 'sources' : files('fuzz-pe-binary.c'), + 'dependencies' : libopenssl_cflags, + }, +] + # The following fuzzers do not work on oss-fuzz. See #11018. if not want_ossfuzz simple_fuzzers += files('fuzz-compress.c') diff --git a/src/growfs/meson.build b/src/growfs/meson.build index 0fcedb19d96d3..7bdc329c36dbb 100644 --- a/src/growfs/meson.build +++ b/src/growfs/meson.build @@ -4,6 +4,9 @@ executables += [ libexec_template + { 'name' : 'systemd-growfs', 'sources' : files('growfs.c'), + 'dependencies' : [ + libcryptsetup_cflags, + ], }, libexec_template + { 'name' : 'systemd-makefs', diff --git a/src/home/meson.build b/src/home/meson.build index b724517ae9ce9..2713b0463514b 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -75,7 +75,9 @@ executables += [ 'objects' : ['systemd-homed'], 'dependencies' : [ libblkid_cflags, + libcryptsetup_cflags, libfdisk_cflags, + libfido2_cflags, libopenssl_cflags, libp11kit_cflags, ], @@ -87,6 +89,7 @@ executables += [ 'extract' : homectl_extract, 'objects' : ['systemd-homed'], 'dependencies' : [ + libfido2_cflags, libopenssl_cflags, libp11kit_cflags, ], diff --git a/src/imds/meson.build b/src/imds/meson.build index a23735d100219..9167600b651ef 100644 --- a/src/imds/meson.build +++ b/src/imds/meson.build @@ -10,6 +10,9 @@ executables += [ 'public' : true, 'sources' : files('imdsd.c'), 'extract' : files('imds-util.c'), + 'dependencies' : [ + libcurl_cflags, + ], }, libexec_template + { 'name' : 'systemd-imds', diff --git a/src/import/meson.build b/src/import/meson.build index f133f276b4be2..e141ffb9750f5 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -17,6 +17,10 @@ executables += [ 'import-common.c', 'qcow2-util.c', ), + 'dependencies' : [ + libarchive_cflags, + libselinux_cflags, + ], }, libexec_template + { 'name' : 'systemd-pull', @@ -30,7 +34,10 @@ executables += [ 'pull-tar.c', ), 'objects' : ['systemd-importd'], - 'dependencies' : libopenssl_cflags, + 'dependencies' : [ + libcurl_cflags, + libopenssl_cflags, + ], }, libexec_template + { 'name' : 'systemd-import', @@ -73,6 +80,9 @@ executables += [ test_template + { 'sources' : files('test-tar.c'), 'type' : 'manual', + 'dependencies' : [ + libarchive_cflags, + ], }, test_template + { 'sources' : files('test-qcow2.c'), diff --git a/src/import/pull-common.h b/src/import/pull-common.h index e4c0d365309d0..f38d4f1bae7d4 100644 --- a/src/import/pull-common.h +++ b/src/import/pull-common.h @@ -1,13 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "shared-forward.h" #include "import-common.h" #include "import-util.h" -#include "pull-job.h" - -typedef struct CurlGlue CurlGlue; -typedef struct PullJob PullJob; +#include "pull-forward.h" int pull_find_old_etags( const char *url, diff --git a/src/import/pull-forward.h b/src/import/pull-forward.h new file mode 100644 index 0000000000000..39ed1725ce784 --- /dev/null +++ b/src/import/pull-forward.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "shared-forward.h" + +typedef enum PullJobState PullJobState; + +typedef struct PullJob PullJob; + +typedef struct OciPull OciPull; +typedef struct RawPull RawPull; +typedef struct TarPull TarPull; + +typedef void (*PullJobFinished)(PullJob *job); +typedef int (*PullJobOpenDisk)(PullJob *job); +typedef int (*PullJobHeader)(PullJob *job, const char *header, size_t sz); +typedef void (*PullJobProgress)(PullJob *job); +typedef int (*PullJobNotFound)(PullJob *job, char **ret_new_url); + +typedef void (*OciPullFinished)(OciPull *pull, int error, void *userdata); +typedef void (*RawPullFinished)(RawPull *p, int error, void *userdata); +typedef void (*TarPullFinished)(TarPull *p, int error, void *userdata); diff --git a/src/import/pull-job.c b/src/import/pull-job.c index 7d83c994ff50a..119bb7528617d 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -15,7 +15,6 @@ #include "io-util.h" #include "log.h" #include "parse-util.h" -#include "pull-common.h" #include "pull-job.h" #include "string-util.h" #include "strv.h" diff --git a/src/import/pull-job.h b/src/import/pull-job.h index 00d001680ff20..2314cc2452916 100644 --- a/src/import/pull-job.h +++ b/src/import/pull-job.h @@ -1,19 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include #include #include -#include "shared-forward.h" - -typedef struct PullJob PullJob; - -typedef void (*PullJobFinished)(PullJob *job); -typedef int (*PullJobOpenDisk)(PullJob *job); -typedef int (*PullJobHeader)(PullJob *job, const char *header, size_t sz); -typedef void (*PullJobProgress)(PullJob *job); -typedef int (*PullJobNotFound)(PullJob *job, char **ret_new_url); +#include "pull-forward.h" typedef enum PullJobState { PULL_JOB_INIT, diff --git a/src/import/pull-oci.c b/src/import/pull-oci.c index b6fa71c902965..a9019f19a51c0 100644 --- a/src/import/pull-oci.c +++ b/src/import/pull-oci.c @@ -28,6 +28,7 @@ #include "pidref.h" #include "process-util.h" #include "pull-common.h" +#include "pull-job.h" #include "pull-oci.h" #include "rm-rf.h" #include "set.h" diff --git a/src/import/pull-oci.h b/src/import/pull-oci.h index 86a37c33ec210..4cf02b97e9189 100644 --- a/src/import/pull-oci.h +++ b/src/import/pull-oci.h @@ -1,12 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "shared-forward.h" #include "import-common.h" - -typedef struct OciPull OciPull; - -typedef void (*OciPullFinished)(OciPull *pull, int error, void *userdata); +#include "pull-forward.h" int oci_pull_new(OciPull **ret, sd_event *event, const char *image_root, OciPullFinished on_finished, void *userdata); OciPull* oci_pull_unref(OciPull *i); diff --git a/src/import/pull-raw.h b/src/import/pull-raw.h index d927d927f89aa..d28ec644ebefd 100644 --- a/src/import/pull-raw.h +++ b/src/import/pull-raw.h @@ -1,13 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "shared-forward.h" #include "import-common.h" #include "import-util.h" - -typedef struct RawPull RawPull; - -typedef void (*RawPullFinished)(RawPull *p, int error, void *userdata); +#include "pull-forward.h" int raw_pull_new(RawPull **ret, sd_event *event, const char *image_root, RawPullFinished on_finished, void *userdata); RawPull* raw_pull_unref(RawPull *p); diff --git a/src/import/pull-tar.h b/src/import/pull-tar.h index 39a9eacb35295..bcb998b82d08f 100644 --- a/src/import/pull-tar.h +++ b/src/import/pull-tar.h @@ -1,13 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "shared-forward.h" #include "import-common.h" #include "import-util.h" - -typedef struct TarPull TarPull; - -typedef void (*TarPullFinished)(TarPull *p, int error, void *userdata); +#include "pull-forward.h" int tar_pull_new(TarPull **ret, sd_event *event, const char *image_root, TarPullFinished on_finished, void *userdata); TarPull* tar_pull_unref(TarPull *p); diff --git a/src/include/meson.build b/src/include/meson.build index 9cf7e4dbc8895..5fc8bb6a61e2b 100644 --- a/src/include/meson.build +++ b/src/include/meson.build @@ -33,11 +33,6 @@ ipproto_sources = files( 'uapi/linux/in.h', ) -# Source files that provides AUDIT_XYZ -audit_sources = files( - 'override/linux/audit.h', -) - # Source files that provides KEY_XYZ keyboard_sources = files( 'uapi/linux/input.h', diff --git a/src/include/override/linux/audit.h b/src/include/override/linux/audit.h deleted file mode 100644 index abdd25b37921a..0000000000000 --- a/src/include/override/linux/audit.h +++ /dev/null @@ -1,33 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include_next /* IWYU pragma: export */ - -#if HAVE_AUDIT -# include /* IWYU pragma: export */ -#endif - -#include - -/* We use static_assert() directly here instead of assert_cc() - * because if we include macro.h in this header, the invocation - * of generate-audit_type-list.sh becomes more complex. - */ - -#ifndef AUDIT_SERVICE_START -# define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ -#else -static_assert(AUDIT_SERVICE_START == 1130, ""); -#endif - -#ifndef AUDIT_SERVICE_STOP -# define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ -#else -static_assert(AUDIT_SERVICE_STOP == 1131, ""); -#endif - -#ifndef MAX_AUDIT_MESSAGE_LENGTH -# define MAX_AUDIT_MESSAGE_LENGTH 8970 -#else -static_assert(MAX_AUDIT_MESSAGE_LENGTH == 8970, ""); -#endif diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index bb1f901d9c867..25465507208d9 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c index 66cc4114f40e4..21974a67edc26 100644 --- a/src/journal-remote/journal-upload-journal.c +++ b/src/journal-remote/journal-upload-journal.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-daemon.h" #include "sd-event.h" #include "sd-journal.h" diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h index 9ab0f535a4c06..3c857dc4b6579 100644 --- a/src/journal-remote/journal-upload.h +++ b/src/journal-remote/journal-upload.h @@ -2,8 +2,7 @@ #pragma once -#include - +#include "curl-util.h" #include "shared-forward.h" #include "journal-compression-util.h" diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index 22ac8703b55d4..1338a3e673cef 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -59,7 +59,10 @@ executables += [ 'sources' : systemd_journal_upload_sources, 'extract' : systemd_journal_upload_extract_sources, 'objects' : ['systemd-journal-remote'], - 'dependencies' : common_deps, + 'dependencies' : [ + common_deps, + libcurl_cflags, + ], }, test_template + { 'sources' : files('test-journal-header-util.c'), diff --git a/src/libsystemd/sd-journal/audit-type.c b/src/journal/audit-type.c similarity index 100% rename from src/libsystemd/sd-journal/audit-type.c rename to src/journal/audit-type.c diff --git a/src/libsystemd/sd-journal/audit-type.h b/src/journal/audit-type.h similarity index 100% rename from src/libsystemd/sd-journal/audit-type.h rename to src/journal/audit-type.h diff --git a/src/libsystemd/sd-journal/audit_type-to-name.awk b/src/journal/audit_type-to-name.awk similarity index 61% rename from src/libsystemd/sd-journal/audit_type-to-name.awk rename to src/journal/audit_type-to-name.awk index 5dbba45af1bc5..879156fd2f097 100644 --- a/src/libsystemd/sd-journal/audit_type-to-name.awk +++ b/src/journal/audit_type-to-name.awk @@ -1,6 +1,13 @@ # SPDX-License-Identifier: LGPL-2.1-or-later BEGIN{ + print "#if HAVE_AUDIT" + print "# ifndef SYSTEMD_CFLAGS_MARKER_LIBAUDIT" + print "# error \"missing libaudit_cflags in meson dependency.\"" + print "# endif" + print "# include " + print "#endif" + print "" print "#include " print "" print "#include \"audit-type.h\"" diff --git a/src/libsystemd/sd-journal/generate-audit_type-list.sh b/src/journal/generate-audit_type-list.sh similarity index 100% rename from src/libsystemd/sd-journal/generate-audit_type-list.sh rename to src/journal/generate-audit_type-list.sh diff --git a/src/journal/journald-manager.c b/src/journal/journald-manager.c index 68f95ea96782e..be3532f1ed5d6 100644 --- a/src/journal/journald-manager.c +++ b/src/journal/journald-manager.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include @@ -47,6 +46,7 @@ #include "journald-sync.h" #include "journald-syslog.h" #include "journald-varlink.h" +#include "libaudit-util.h" #include "log.h" #include "log-ratelimit.h" #include "memory-util.h" diff --git a/src/journal/meson.build b/src/journal/meson.build index e39381f03f633..41561deb8b3b7 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -4,6 +4,7 @@ systemd_journald_sources = files( 'journald.c', ) systemd_journald_extract_sources = files( + 'audit-type.c', 'journald-audit.c', 'journald-client.c', 'journald-config.c', @@ -29,6 +30,31 @@ journald_gperf_c = custom_target( generated_sources += journald_gperf_c systemd_journald_extract_sources += journald_gperf_c +generate_audit_type_list = files('generate-audit_type-list.sh') +generate_audit_type_command = [env, 'bash', generate_audit_type_list, cpp] +if libaudit.found() + generate_audit_type_command += [ + '-include', 'libaudit.h', + '-I' + libaudit.get_variable('includedir'), + ] +endif +generate_audit_type_command += system_include_args + +audit_type_list_txt = custom_target( + input : [generate_audit_type_list], + output : 'audit_type-list.txt', + command : generate_audit_type_command, + capture : true) + +audit_type_to_name = custom_target( + input : ['audit_type-to-name.awk', audit_type_list_txt], + output : 'audit_type-to-name.inc', + command : [awk, '-f', '@INPUT0@', '@INPUT1@'], + capture : true) + +generated_sources += audit_type_to_name +systemd_journald_extract_sources += audit_type_to_name + journalctl_sources = files( 'journalctl.c', 'journalctl-authenticate.c', @@ -69,7 +95,10 @@ executables += [ 'sources' : systemd_journald_sources, 'extract' : systemd_journald_extract_sources, 'dependencies' : [ + libacl_cflags, + libaudit_cflags, liblz4_cflags, + libpcre2_cflags, libselinux_cflags, libxz_cflags, libzstd_cflags, @@ -80,6 +109,9 @@ executables += [ 'public' : true, 'conditions' : ['HAVE_QRENCODE'], 'sources' : files('bsod.c'), + 'dependencies' : [ + libqrencode_cflags, + ], }, executable_template + { 'name' : 'systemd-cat', @@ -95,10 +127,14 @@ executables += [ 'dependencies' : [ liblz4_cflags, libopenssl_cflags, + libpcre2_cflags, libxz_cflags, libzstd_cflags, ], }, + journal_test_template + { + 'sources' : files('test-audit-type.c'), + }, journal_test_template + { 'sources' : files('test-journald-config.c'), 'dependencies' : [ diff --git a/src/libsystemd/sd-journal/test-audit-type.c b/src/journal/test-audit-type.c similarity index 100% rename from src/libsystemd/sd-journal/test-audit-type.c rename to src/journal/test-audit-type.c diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index d1797722a42da..cd3821bb8d6a5 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -1,7 +1,6 @@ # SPDX-License-Identifier: LGPL-2.1-or-later sd_journal_sources = files( - 'sd-journal/audit-type.c', 'sd-journal/catalog.c', 'sd-journal/journal-authenticate-internal.c', 'sd-journal/journal-file.c', @@ -13,11 +12,6 @@ sd_journal_sources = files( 'sd-journal/sd-journal.c', ) -subdir('sd-journal') - -generated_sources += audit_type_to_name -sd_journal_sources += audit_type_to_name - ############################################################ sd_id128_sources = files( @@ -197,7 +191,6 @@ simple_tests += files( 'sd-future/test-fiber-ops.c', 'sd-hwdb/test-sd-hwdb.c', 'sd-id128/test-id128.c', - 'sd-journal/test-audit-type.c', 'sd-journal/test-catalog.c', 'sd-journal/test-journal-file.c', 'sd-journal/test-journal-flush.c', @@ -233,9 +226,7 @@ libsystemd_tests += [ 'sources' : files('sd-bus/test-bus-marshal.c'), 'dependencies' : [ libdbus_cflags, - libgio_cflags, libglib_cflags, - libgobject_cflags, ], }, { diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 05074fd92b2f6..064b76d3e000e 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -5,12 +5,18 @@ #include "macro.h" #if HAVE_GLIB +#ifndef SYSTEMD_CFLAGS_MARKER_LIBGLIB +# error "missing libglib_cflags in meson dependency." +#endif DISABLE_WARNING_FORMAT_NONLITERAL #include /* NOLINT */ REENABLE_WARNING #endif #if HAVE_DBUS +#ifndef SYSTEMD_CFLAGS_MARKER_LIBDBUS +# error "missing libdbus_cflags in meson dependency." +#endif #include #endif diff --git a/src/libsystemd/sd-journal/meson.build b/src/libsystemd/sd-journal/meson.build deleted file mode 100644 index 61ddf66b59c35..0000000000000 --- a/src/libsystemd/sd-journal/meson.build +++ /dev/null @@ -1,26 +0,0 @@ -# SPDX-License-Identifier: LGPL-2.1-or-later - -# We're generating a header file here intended to be included -# by audit-type.c. To make that work, we have to add the generated -# header file's directory to the include directories for libsystemd. -# In meson, adding include directories is done via include_directories(), -# which always adds two include directories for each argument, one relative to -# the source directory and one relative to the build directory. We don't -# want to add src/libsystemd to the include directories, yet custom_target() -# does not allow path segments in its output argument, so to make sure the -# generated header is written to src/libsystemd/sd-journal in the build directory, -# the custom_target() has to be defined here in src/libsystemd/sd-journal -# in the source directory. - -generate_audit_type_list = files('generate-audit_type-list.sh') -audit_type_list_txt = custom_target( - input : [generate_audit_type_list, audit_sources], - output : 'audit_type-list.txt', - command : [env, 'bash', generate_audit_type_list, cpp, system_include_args], - capture : true) - -audit_type_to_name = custom_target( - input : ['audit_type-to-name.awk', audit_type_list_txt], - output : 'audit_type-to-name.inc', - command : [awk, '-f', '@INPUT0@', '@INPUT1@'], - capture : true) diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index 55f9b8b1d0f4e..95b6b8d039f4d 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -392,23 +392,29 @@ static int varlink_idl_format_symbol( * Until this is resolved upstream, consider this comment part of the API (i.e. don't change * only extend). It is used by tools like varlink-http-bridge. */ if ((symbol->symbol_flags & (SD_VARLINK_REQUIRES_MORE|SD_VARLINK_SUPPORTS_MORE)) != 0) { - fputs(colors[COLOR_COMMENT], f); - if (FLAGS_SET(symbol->symbol_flags, SD_VARLINK_REQUIRES_MORE)) - fputs("# [Requires 'more' flag]", f); - else - fputs("# [Supports 'more' flag]", f); - fputs(colors[COLOR_RESET], f); - fputs("\n", f); + const char *msg = FLAGS_SET(symbol->symbol_flags, SD_VARLINK_REQUIRES_MORE) ? "[Requires 'more' flag]" : "[Supports 'more' flag]"; + + r = varlink_idl_format_comment( + f, + msg, + /* indent= */ NULL, + colors, + cols); + if (r < 0) + return r; } if ((symbol->symbol_flags & (SD_VARLINK_REQUIRES_UPGRADE|SD_VARLINK_SUPPORTS_UPGRADE)) != 0) { - fputs(colors[COLOR_COMMENT], f); - if (FLAGS_SET(symbol->symbol_flags, SD_VARLINK_REQUIRES_UPGRADE)) - fputs("# [Requires 'upgrade' flag]", f); - else - fputs("# [Supports 'upgrade' flag]", f); - fputs(colors[COLOR_RESET], f); - fputs("\n", f); + const char *msg = FLAGS_SET(symbol->symbol_flags, SD_VARLINK_REQUIRES_UPGRADE) ? "[Requires 'upgrade' flag]" : "[Supports 'upgrade' flag]"; + + r = varlink_idl_format_comment( + f, + msg, + /* indent= */ NULL, + colors, + cols); + if (r < 0) + return r; } fputs(colors[COLOR_SYMBOL_TYPE], f); diff --git a/src/libsystemd/sd-varlink/test-varlink-idl.c b/src/libsystemd/sd-varlink/test-varlink-idl.c index ae4a6d21b80f7..0619e6d8bf5f4 100644 --- a/src/libsystemd/sd-varlink/test-varlink-idl.c +++ b/src/libsystemd/sd-varlink/test-varlink-idl.c @@ -52,6 +52,7 @@ #include "varlink-io.systemd.Resolve.Monitor.h" #include "varlink-io.systemd.Shutdown.h" #include "varlink-io.systemd.StorageProvider.h" +#include "varlink-io.systemd.SysInstall.h" #include "varlink-io.systemd.SysUpdate.h" #include "varlink-io.systemd.SysUpdate.Notify.h" #include "varlink-io.systemd.Udev.h" @@ -231,6 +232,7 @@ TEST(parse_format) { &vl_interface_io_systemd_Resolve_Monitor, &vl_interface_io_systemd_Shutdown, &vl_interface_io_systemd_StorageProvider, + &vl_interface_io_systemd_SysInstall, &vl_interface_io_systemd_SysUpdate, &vl_interface_io_systemd_SysUpdate_Notify, &vl_interface_io_systemd_Udev, diff --git a/src/locale/xkbcommon-util.h b/src/locale/xkbcommon-util.h index 1a709ae50c03e..e441e9d89bac6 100644 --- a/src/locale/xkbcommon-util.h +++ b/src/locale/xkbcommon-util.h @@ -5,6 +5,10 @@ #include "shared-forward.h" #if HAVE_XKBCOMMON +#ifndef SYSTEMD_CFLAGS_MARKER_LIBXKBCOMMON +# error "missing libxkbcommon_cflags in meson dependency." +#endif + #include extern DLSYM_PROTOTYPE(xkb_context_new); diff --git a/src/login/meson.build b/src/login/meson.build index 44325ccd7c02f..effb44c3a8314 100644 --- a/src/login/meson.build +++ b/src/login/meson.build @@ -48,6 +48,9 @@ executables += [ 'dbus' : true, 'sources' : systemd_logind_sources, 'extract' : systemd_logind_extract_sources, + 'dependencies' : [ + libacl_cflags, + ], }, executable_template + { 'name' : 'loginctl', diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index a73f81b6f7225..a801978fafd0a 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -126,6 +126,7 @@ static int machine_cid(const char *name, sd_json_variant *variant, sd_json_dispa int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { Manager *manager = ASSERT_PTR(userdata); _cleanup_(machine_freep) Machine *machine = NULL; + const char *bad_field = NULL; bool sender_is_admin = false; int r; @@ -136,8 +137,6 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink { "class", SD_JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), SD_JSON_MANDATORY }, { "leader", _SD_JSON_VARIANT_TYPE_INVALID, machine_pidref, offsetof(Machine, leader), SD_JSON_STRICT }, { "leaderProcessId", SD_JSON_VARIANT_OBJECT, machine_pidref, offsetof(Machine, leader), SD_JSON_STRICT }, - { "supervisor", _SD_JSON_VARIANT_TYPE_INVALID, machine_pidref, offsetof(Machine, supervisor), SD_JSON_STRICT }, - { "supervisorProcessId", SD_JSON_VARIANT_OBJECT, machine_pidref, offsetof(Machine, supervisor), SD_JSON_STRICT }, { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, root_directory), SD_JSON_STRICT }, { "ifIndices", SD_JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, { "vSockCid", _SD_JSON_VARIANT_TYPE_INVALID, machine_cid, offsetof(Machine, vsock_cid), 0 }, @@ -153,9 +152,12 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink if (r < 0) return r; - r = sd_varlink_dispatch(link, parameters, dispatch_table, machine); - if (r != 0) + r = sd_json_dispatch_full(parameters, dispatch_table, /* bad= */ NULL, SD_JSON_ALLOW_EXTENSIONS, machine, &bad_field); + if (r < 0) { + if (bad_field) + return sd_varlink_error_invalid_parameter_name(link, bad_field); return r; + } if (!MACHINE_CLASS_CAN_REGISTER(machine->class)) return sd_varlink_error_invalid_parameter_name(link, "class"); @@ -179,9 +181,7 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink r = varlink_get_peer_pidref(link, &machine->leader); if (r < 0) return r; - } - - if (!pidref_is_set(&machine->supervisor)) { + } else { _cleanup_(pidref_done) PidRef client_pidref = PIDREF_NULL; r = varlink_get_peer_pidref(link, &client_pidref); diff --git a/src/measure/meson.build b/src/measure/meson.build index ff777dc5d9842..3dd793e8c3574 100644 --- a/src/measure/meson.build +++ b/src/measure/meson.build @@ -9,6 +9,9 @@ executables += [ 'HAVE_TPM2', ], 'sources' : files('measure-tool.c'), - 'dependencies' : libopenssl_cflags, + 'dependencies' : [ + libopenssl_cflags, + tpm2_cflags, + ], }, ] diff --git a/src/network/meson.build b/src/network/meson.build index 110af9511c11b..b620e512df776 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -204,9 +204,12 @@ executables += [ libsystemd_network, networkd_link_with, ], + 'dependencies' : [ + libbpf_cflags, + ], 'bpf_programs': [ 'sysctl-monitor', - ] + ], }, libexec_template + { 'name' : 'systemd-networkd-wait-online', @@ -225,6 +228,9 @@ executables += [ libsystemd_network, networkd_link_with, ], + 'dependencies' : [ + libselinux_cflags, + ], }, libexec_template + { 'name' : 'systemd-network-generator', diff --git a/src/nsresourced/meson.build b/src/nsresourced/meson.build index 1654e1766b1eb..2037d06bc8a41 100644 --- a/src/nsresourced/meson.build +++ b/src/nsresourced/meson.build @@ -24,6 +24,9 @@ executables += [ 'name' : 'systemd-nsresourced', 'sources' : systemd_nsresourced_sources, 'extract' : systemd_nsresourced_extract_sources, + 'dependencies' : [ + libbpf_cflags, + ], 'bpf_programs': ['userns-restrict'], }, libexec_template + { diff --git a/src/pcrextend/meson.build b/src/pcrextend/meson.build index f2f5f3b46e3e8..ced1be5a90909 100644 --- a/src/pcrextend/meson.build +++ b/src/pcrextend/meson.build @@ -12,7 +12,7 @@ executables += [ 'sources' : files('pcrextend.c'), 'dependencies' : [ libopenssl_cflags, - tpm2, + tpm2_cflags, ], }, ] diff --git a/src/pcrlock/meson.build b/src/pcrlock/meson.build index dbe816402deca..d93a4f6069e62 100644 --- a/src/pcrlock/meson.build +++ b/src/pcrlock/meson.build @@ -13,7 +13,7 @@ executables += [ ), 'dependencies' : [ libopenssl_cflags, - tpm2, + tpm2_cflags, ], 'public' : true, }, diff --git a/src/repart/meson.build b/src/repart/meson.build index 6d3278c3ca65f..721fe73b9ae9e 100644 --- a/src/repart/meson.build +++ b/src/repart/meson.build @@ -15,9 +15,11 @@ executables += [ ), 'dependencies' : [ libblkid_cflags, + libcryptsetup_cflags, libfdisk_cflags, libmount_cflags, libopenssl_cflags, + tpm2_cflags, ], }, executable_template + { @@ -32,9 +34,11 @@ executables += [ ], 'dependencies' : [ libblkid_cflags, + libcryptsetup_cflags, libfdisk_cflags, libmount_cflags, libopenssl_cflags, + tpm2_cflags, ], }, ] diff --git a/src/report/meson.build b/src/report/meson.build index d0a316c6897d2..8a4716ec21c0a 100644 --- a/src/report/meson.build +++ b/src/report/meson.build @@ -10,6 +10,9 @@ executables += [ 'report-sign.c', 'report-upload.c', ), + 'dependencies' : [ + libcurl_cflags, + ], }, libexec_template + { diff --git a/src/report/report-basic.c b/src/report/report-basic.c index 56f7065c68d31..cc3cdd8b243a4 100644 --- a/src/report/report-basic.c +++ b/src/report/report-basic.c @@ -505,7 +505,6 @@ static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, voi METRIC_IO_SYSTEMD_BASIC_PREFIX "OSRelease." name, \ "Operating system identification (" name "= field from os-release)", \ METRIC_FAMILY_TYPE_STRING, \ - .generate = NULL, \ } #define MACHINE_INFO_STANDARD_FIELD(name) \ @@ -513,7 +512,6 @@ static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, voi METRIC_IO_SYSTEMD_BASIC_PREFIX "MachineInfo." name, \ "Machine identification (" name "= field from machine-info)", \ METRIC_FAMILY_TYPE_STRING, \ - .generate = NULL, \ } #define SMBIOS_STANDARD_FIELD(name) \ @@ -521,7 +519,6 @@ static int virtualization_generate(const MetricFamily *mf, sd_varlink *link, voi METRIC_IO_SYSTEMD_BASIC_PREFIX "SMBIOS." name, \ "Firmware/hardware identification (" name " field from SMBIOS/DMI)", \ METRIC_FAMILY_TYPE_STRING, \ - .generate = NULL, \ } static const MetricFamily metric_family_table[] = { @@ -572,13 +569,11 @@ static const MetricFamily metric_family_table[] = { METRIC_IO_SYSTEMD_BASIC_PREFIX "LoadAverage5Min", "System load average over the last 5 minutes", METRIC_FAMILY_TYPE_GAUGE, - .generate = NULL, }, { METRIC_IO_SYSTEMD_BASIC_PREFIX "LoadAverage15Min", "System load average over the last 15 minutes", METRIC_FAMILY_TYPE_GAUGE, - .generate = NULL, }, /* Keep those ↑ in sync with load_average_generate(). */ { @@ -665,7 +660,6 @@ static const MetricFamily metric_family_table[] = { METRIC_IO_SYSTEMD_BASIC_PREFIX "TPM2.VendorString", "TPM2 device vendor string (ID_TPM2_VENDOR_STRING property of the tpmrm0 device)", METRIC_FAMILY_TYPE_STRING, - .generate = NULL, }, /* Keep those ↑ in sync with tpm2_generate(). */ { diff --git a/src/report/report-cgroup.c b/src/report/report-cgroup.c index 240f09f9f2a4f..94aa0869c4540 100644 --- a/src/report/report-cgroup.c +++ b/src/report/report-cgroup.c @@ -370,25 +370,21 @@ static const MetricFamily cgroup_metric_family_table[] = { METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadBytes", "Per unit metric: IO bytes read", METRIC_FAMILY_TYPE_COUNTER, - .generate = NULL, }, { METRIC_IO_SYSTEMD_CGROUP_PREFIX "IOReadOperations", "Per unit metric: IO read operations", METRIC_FAMILY_TYPE_COUNTER, - .generate = NULL, }, { METRIC_IO_SYSTEMD_CGROUP_PREFIX "MemoryUsage", "Per unit metric: memory usage in bytes", METRIC_FAMILY_TYPE_GAUGE, - .generate = NULL, }, { METRIC_IO_SYSTEMD_CGROUP_PREFIX "TasksCurrent", "Per unit metric: current number of tasks", METRIC_FAMILY_TYPE_GAUGE, - .generate = NULL, }, {} }; diff --git a/src/resolve/meson.build b/src/resolve/meson.build index f024b02047c7b..019c4b7bb06c4 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -131,6 +131,10 @@ executables += [ 'sources' : files('test-dnssec-complex.c'), 'type' : 'manual', }, + resolve_test_template + { + 'sources' : files('test-dnssec-crypto.c'), + 'conditions' : ['HAVE_OPENSSL'], + }, resolve_test_template + { 'sources' : files('test-dns-search-domain.c'), }, diff --git a/src/resolve/resolved-dns-dnssec-crypto.h b/src/resolve/resolved-dns-dnssec-crypto.h new file mode 100644 index 0000000000000..87dd6c8db5ea4 --- /dev/null +++ b/src/resolve/resolved-dns-dnssec-crypto.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "crypto-util.h" +#include "shared-forward.h" + +#if HAVE_OPENSSL + +int dnssec_rsa_verify_raw( + const EVP_MD *hash_algorithm, + const struct iovec *signature, + const struct iovec *hash, + const struct iovec *exponent, + const struct iovec *modulus); + +int dnssec_ecdsa_verify_raw( + const EVP_MD *hash_algorithm, + int curve, + const struct iovec *signature_r, + const struct iovec *signature_s, + const struct iovec *hash, + const struct iovec *key); + +#endif diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index 36eb3595d5bec..67cf991e70596 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -12,12 +12,12 @@ #include "memory-util.h" #include "memstream-util.h" #include "resolved-dns-dnssec.h" +#include "resolved-dns-dnssec-crypto.h" #include "sort-util.h" #include "string-table.h" #include "string-util.h" #include "time-util.h" - #define VERIFY_RRS_MAX 256 #define MAX_KEY_SIZE (32*1024) @@ -73,49 +73,58 @@ static int dnssec_verify_errno(int r) { return r == -EOPNOTSUPP ? -EIO : r; } -static int dnssec_rsa_verify_raw( +int dnssec_rsa_verify_raw( const EVP_MD *hash_algorithm, - const void *signature, size_t signature_size, - const void *data, size_t data_size, - const void *exponent, size_t exponent_size, - const void *modulus, size_t modulus_size) { - int r; + const struct iovec *signature, + const struct iovec *hash, + const struct iovec *exponent, + const struct iovec *modulus) { - DISABLE_WARNING_DEPRECATED_DECLARATIONS; - _cleanup_(RSA_freep) RSA *rpubkey = NULL; - _cleanup_(EVP_PKEY_freep) EVP_PKEY *epubkey = NULL; - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL; - _cleanup_(BN_freep) BIGNUM *e = NULL, *m = NULL; + int r; assert(hash_algorithm); + assert(iovec_is_set(signature)); + assert(iovec_is_set(hash)); + assert(iovec_is_set(exponent)); + assert(iovec_is_set(modulus)); - e = sym_BN_bin2bn(exponent, exponent_size, NULL); + _cleanup_(BN_freep) BIGNUM *e = sym_BN_bin2bn(exponent->iov_base, exponent->iov_len, NULL); if (!e) return log_openssl_errors(LOG_DEBUG, "Failed to convert RSA exponent to BIGNUM"); - m = sym_BN_bin2bn(modulus, modulus_size, NULL); + _cleanup_(BN_freep) BIGNUM *m = sym_BN_bin2bn(modulus->iov_base, modulus->iov_len, NULL); if (!m) return log_openssl_errors(LOG_DEBUG, "Failed to convert RSA modulus to BIGNUM"); - rpubkey = sym_RSA_new(); - if (!rpubkey) + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) return -ENOMEM; - if (sym_RSA_set0_key(rpubkey, m, e, NULL) <= 0) - return log_openssl_errors(LOG_DEBUG, "Failed to set RSA public key"); - e = m = NULL; + if (sym_OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to push RSA exponent to OSSL_PARAM_BLD"); - if ((size_t) sym_RSA_size(rpubkey) != signature_size) - return -EINVAL; + if (sym_OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, m) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to push RSA modulus to OSSL_PARAM_BLD"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_openssl_errors(LOG_DEBUG, "Failed to generate OSSL param"); - epubkey = sym_EVP_PKEY_new(); - if (!epubkey) + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *kctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!kctx) return -ENOMEM; - if (sym_EVP_PKEY_assign_RSA(epubkey, sym_RSAPublicKey_dup(rpubkey)) <= 0) - return log_openssl_errors(LOG_DEBUG, "Failed to assign RSA public key"); + if (sym_EVP_PKEY_fromdata_init(kctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize key creation"); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *epubkey = NULL; + if (sym_EVP_PKEY_fromdata(kctx, &epubkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to load RSA public key from raw data"); + + if ((size_t) sym_EVP_PKEY_get_size(epubkey) != signature->iov_len) + return -EINVAL; - ctx = sym_EVP_PKEY_CTX_new(epubkey, NULL); + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = sym_EVP_PKEY_CTX_new(epubkey, NULL); if (!ctx) return -ENOMEM; @@ -128,17 +137,16 @@ static int dnssec_rsa_verify_raw( if (sym_EVP_PKEY_CTX_set_signature_md(ctx, hash_algorithm) <= 0) return log_openssl_errors(LOG_DEBUG, "Failed to set RSA signature digest"); - r = sym_EVP_PKEY_verify(ctx, signature, signature_size, data, data_size); + r = sym_EVP_PKEY_verify(ctx, signature->iov_base, signature->iov_len, hash->iov_base, hash->iov_len); if (r < 0) return log_openssl_errors(LOG_DEBUG, "Signature verification failed"); - REENABLE_WARNING; return r; } static int dnssec_rsa_verify( const EVP_MD *hash_algorithm, - const void *hash, size_t hash_size, + const struct iovec *hash, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { @@ -146,8 +154,7 @@ static int dnssec_rsa_verify( void *exponent, *modulus; assert(hash_algorithm); - assert(hash); - assert(hash_size > 0); + assert(iovec_is_set(hash)); assert(rrsig); assert(dnskey); @@ -191,89 +198,96 @@ static int dnssec_rsa_verify( return dnssec_rsa_verify_raw( hash_algorithm, - rrsig->rrsig.signature, rrsig->rrsig.signature_size, - hash, hash_size, - exponent, exponent_size, - modulus, modulus_size); + &IOVEC_MAKE(rrsig->rrsig.signature, rrsig->rrsig.signature_size), + hash, + &IOVEC_MAKE(exponent, exponent_size), + &IOVEC_MAKE(modulus, modulus_size)); } -static int dnssec_ecdsa_verify_raw( +int dnssec_ecdsa_verify_raw( const EVP_MD *hash_algorithm, int curve, - const void *signature_r, size_t signature_r_size, - const void *signature_s, size_t signature_s_size, - const void *data, size_t data_size, - const void *key, size_t key_size) { - int k; + const struct iovec *signature_r, + const struct iovec *signature_s, + const struct iovec *hash, + const struct iovec *key) { - DISABLE_WARNING_DEPRECATED_DECLARATIONS; - _cleanup_(EC_GROUP_freep) EC_GROUP *ec_group = NULL; - _cleanup_(EC_POINT_freep) EC_POINT *p = NULL; - _cleanup_(EC_KEY_freep) EC_KEY *eckey = NULL; - _cleanup_(BN_CTX_freep) BN_CTX *bctx = NULL; - _cleanup_(BN_freep) BIGNUM *r = NULL, *s = NULL; - _cleanup_(ECDSA_SIG_freep) ECDSA_SIG *sig = NULL; + int r; assert(hash_algorithm); - - ec_group = sym_EC_GROUP_new_by_curve_name(curve); - if (!ec_group) - return -ENOMEM; - - p = sym_EC_POINT_new(ec_group); - if (!p) + assert(iovec_is_set(signature_r)); + assert(iovec_is_set(signature_s)); + assert(iovec_is_set(hash)); + assert(iovec_is_set(key)); + + const char *curve_name = sym_OBJ_nid2sn(curve); + if (!curve_name) + return log_openssl_errors(LOG_DEBUG, "Unknown curve NID"); + + OSSL_PARAM params[] = { + sym_OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*) curve_name, 0), + sym_OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, key->iov_base, key->iov_len), + sym_OSSL_PARAM_construct_end(), + }; + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *kctx = sym_EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + if (!kctx) return -ENOMEM; - bctx = sym_BN_CTX_new(); - if (!bctx) - return -ENOMEM; - - if (sym_EC_POINT_oct2point(ec_group, p, key, key_size, bctx) <= 0) - return log_openssl_errors(LOG_DEBUG, "Failed to parse EC public key point"); - - eckey = sym_EC_KEY_new(); - if (!eckey) - return -ENOMEM; - - if (sym_EC_KEY_set_group(eckey, ec_group) <= 0) - return log_openssl_errors(LOG_DEBUG, "Failed to set EC group"); + if (sym_EVP_PKEY_fromdata_init(kctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize EC key creation"); - if (sym_EC_KEY_set_public_key(eckey, p) <= 0) - return log_openssl_errors(LOG_DEBUG, "EC_KEY_set_public_key failed"); - - if (sym_EC_KEY_check_key(eckey) != 1) - return log_openssl_errors(LOG_DEBUG, "EC_KEY_check_key failed"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *epubkey = NULL; + if (sym_EVP_PKEY_fromdata(kctx, &epubkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to load EC public key from raw data"); - r = sym_BN_bin2bn(signature_r, signature_r_size, NULL); - if (!r) + _cleanup_(BN_freep) BIGNUM *bn_r = sym_BN_bin2bn(signature_r->iov_base, signature_r->iov_len, NULL); + if (!bn_r) return log_openssl_errors(LOG_DEBUG, "Failed to convert ECDSA signature r to BIGNUM"); - s = sym_BN_bin2bn(signature_s, signature_s_size, NULL); - if (!s) + _cleanup_(BN_freep) BIGNUM *bn_s = sym_BN_bin2bn(signature_s->iov_base, signature_s->iov_len, NULL); + if (!bn_s) return log_openssl_errors(LOG_DEBUG, "Failed to convert ECDSA signature s to BIGNUM"); - /* TODO: We should eventually use the EVP API once it supports ECDSA signature verification */ - - sig = sym_ECDSA_SIG_new(); + _cleanup_(ECDSA_SIG_freep) ECDSA_SIG *sig = sym_ECDSA_SIG_new(); if (!sig) return -ENOMEM; - if (sym_ECDSA_SIG_set0(sig, r, s) <= 0) + if (sym_ECDSA_SIG_set0(sig, bn_r, bn_s) <= 0) return log_openssl_errors(LOG_DEBUG, "Failed to set ECDSA signature"); - r = s = NULL; + TAKE_PTR(bn_r); + TAKE_PTR(bn_s); + + _cleanup_(OPENSSL_freep) void *buf = NULL; + r = sym_i2d_ECDSA_SIG(sig, (unsigned char**) &buf); + if (r <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to DER encode ECDSA signature"); + struct iovec der_sig = IOVEC_MAKE(buf, r); + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *vctx = sym_EVP_PKEY_CTX_new(epubkey, NULL); + if (!vctx) + return -ENOMEM; + + if (sym_EVP_PKEY_public_check(vctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "EC public key validation failed"); + + if (sym_EVP_PKEY_verify_init(vctx) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to initialize ECDSA signature verification"); - k = sym_ECDSA_do_verify(data, data_size, sig, eckey); - if (k < 0) + if (sym_EVP_PKEY_CTX_set_signature_md(vctx, hash_algorithm) <= 0) + return log_openssl_errors(LOG_DEBUG, "Failed to set ECDSA signature digest"); + + r = sym_EVP_PKEY_verify(vctx, der_sig.iov_base, der_sig.iov_len, hash->iov_base, hash->iov_len); + if (r < 0) return log_openssl_errors(LOG_DEBUG, "Signature verification failed"); - REENABLE_WARNING; - return k; + return r; } static int dnssec_ecdsa_verify( const EVP_MD *hash_algorithm, int algorithm, - const void *hash, size_t hash_size, + const struct iovec *hash, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey) { @@ -281,8 +295,8 @@ static int dnssec_ecdsa_verify( size_t key_size; uint8_t *q; - assert(hash); - assert(hash_size); + assert(hash_algorithm); + assert(iovec_is_set(hash)); assert(rrsig); assert(dnskey); @@ -308,10 +322,10 @@ static int dnssec_ecdsa_verify( return dnssec_ecdsa_verify_raw( hash_algorithm, curve, - rrsig->rrsig.signature, key_size, - (uint8_t*) rrsig->rrsig.signature + key_size, key_size, - hash, hash_size, - q, key_size*2+1); + &IOVEC_MAKE(rrsig->rrsig.signature, key_size), + &IOVEC_MAKE((uint8_t*) rrsig->rrsig.signature + key_size, key_size), + hash, + &IOVEC_MAKE(q, key_size * 2 + 1)); } static int dnssec_eddsa_verify_raw( @@ -677,7 +691,7 @@ static int dnssec_rrset_verify_sig( case DNSSEC_ALGORITHM_RSASHA512: return dnssec_verify_errno(dnssec_rsa_verify( md_algorithm, - hash, hash_size, + &IOVEC_MAKE(hash, hash_size), rrsig, dnskey)); @@ -686,7 +700,7 @@ static int dnssec_rrset_verify_sig( return dnssec_verify_errno(dnssec_ecdsa_verify( md_algorithm, rrsig->rrsig.algorithm, - hash, hash_size, + &IOVEC_MAKE(hash, hash_size), rrsig, dnskey)); diff --git a/src/resolve/test-dnssec-crypto.c b/src/resolve/test-dnssec-crypto.c new file mode 100644 index 0000000000000..82a91a6386c26 --- /dev/null +++ b/src/resolve/test-dnssec-crypto.c @@ -0,0 +1,402 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#if HAVE_VALGRIND_VALGRIND_H +# include +#endif + +#include "hexdecoct.h" +#include "resolved-dns-dnssec-crypto.h" +#include "tests.h" + +static void iovec_dump_c(const char *name, const struct iovec *iov) { + assert(iovec_is_set(iov)); + + printf("static const uint8_t %s[] = {", name); + const uint8_t *buf = iov->iov_base; + for (size_t i = 0; i < iov->iov_len; i++) { + if (i % 8 == 0) + fputs("\n ", stdout); + else + putchar(' '); + + fputs("0x", stdout); + putchar(hexchar((buf[i] & 0xf0) >> 4)); + putchar(hexchar((buf[i] & 0x0f) >> 0)); + putchar(','); + } + puts("\n};\n"); +} + +static void export_bn(const BIGNUM *x, struct iovec *ret) { + assert(x); + assert(ret); + + _cleanup_(iovec_done) struct iovec iov = {}; + ASSERT_OK(iovec_alloc(sym_BN_num_bytes(x), &iov)); + ASSERT_EQ(sym_BN_bn2bin(x, iov.iov_base), (int) iov.iov_len); + + *ret = TAKE_STRUCT(iov); +} + +static const uint8_t test_signature_buf[] = { + 0xa6, 0x6c, 0x60, 0xec, 0x89, 0x90, 0x74, 0xba, + 0x9a, 0x6b, 0x7b, 0xbe, 0xd5, 0x46, 0xfb, 0x5c, + 0xa1, 0x80, 0x15, 0x4c, 0x98, 0x1e, 0xae, 0x13, + 0x41, 0xf1, 0xd3, 0x9e, 0xe6, 0x4f, 0x57, 0x47, + 0x4b, 0xef, 0x3e, 0xe3, 0xdf, 0xef, 0x5e, 0x2f, + 0x76, 0xb2, 0x4a, 0x65, 0x72, 0x81, 0x19, 0xd7, + 0x6e, 0x1c, 0xe7, 0xed, 0x4b, 0x36, 0xd1, 0x06, + 0xaa, 0x86, 0x05, 0xbe, 0x9c, 0x8b, 0x69, 0x80, + 0x30, 0x7f, 0x13, 0xb0, 0x6f, 0xc8, 0x53, 0x42, + 0x00, 0xe2, 0x9e, 0xc6, 0x64, 0xcf, 0x83, 0xac, + 0x83, 0x38, 0x9a, 0xe7, 0xdf, 0x4a, 0x80, 0x08, + 0x22, 0x74, 0x6c, 0x14, 0x20, 0xfe, 0x7d, 0x92, + 0x5f, 0x53, 0xa8, 0xb6, 0x59, 0xd5, 0xae, 0x08, + 0x11, 0x30, 0x0f, 0xf2, 0x5d, 0x3d, 0x69, 0xa6, + 0x94, 0xf5, 0xfa, 0x58, 0xe3, 0x70, 0x75, 0x5b, + 0x68, 0xb1, 0x47, 0x10, 0x72, 0xbf, 0x97, 0xc5, + 0x96, 0x97, 0x05, 0x9a, 0xd5, 0x36, 0xa5, 0x84, + 0x99, 0x4d, 0x0d, 0xe9, 0x47, 0xeb, 0xa8, 0xac, + 0x63, 0xad, 0x70, 0x0d, 0xc1, 0x45, 0xf2, 0x84, + 0x5f, 0x58, 0xcc, 0xcc, 0x77, 0xb4, 0x46, 0xc2, + 0x7e, 0x30, 0xb6, 0x35, 0x76, 0x8c, 0xe8, 0x27, + 0xa3, 0x86, 0x32, 0x29, 0xe7, 0x72, 0x5e, 0x41, + 0x1f, 0x5d, 0x81, 0x35, 0xe8, 0x46, 0x17, 0x55, + 0x51, 0xc8, 0x88, 0x9e, 0x72, 0x9a, 0x78, 0xc2, + 0xed, 0x3c, 0x32, 0x54, 0xd6, 0x46, 0x94, 0xe1, + 0x9c, 0xce, 0xc1, 0x49, 0x2c, 0x43, 0x53, 0x7b, + 0x7d, 0xe3, 0xc5, 0x7e, 0x76, 0x5b, 0x4b, 0xe0, + 0xac, 0x3a, 0xaf, 0xea, 0xfd, 0xc6, 0x62, 0x64, + 0x0c, 0x17, 0xb7, 0x37, 0x6c, 0x1a, 0x7d, 0x69, + 0xea, 0x84, 0x54, 0x1b, 0xd5, 0x0e, 0x9e, 0x76, + 0x0a, 0x12, 0xb7, 0x29, 0xb4, 0xd2, 0x61, 0xc4, + 0x38, 0xb6, 0xfc, 0x34, 0xd2, 0x20, 0xb0, 0x90, +}; + +static const uint8_t test_digest_buf[] = { + 0x64, 0xec, 0x88, 0xca, 0x00, 0xb2, 0x68, 0xe5, + 0xba, 0x1a, 0x35, 0x67, 0x8a, 0x1b, 0x53, 0x16, + 0xd2, 0x12, 0xf4, 0xf3, 0x66, 0xb2, 0x47, 0x72, + 0x32, 0x53, 0x4a, 0x8a, 0xec, 0xa3, 0x7f, 0x3c, +}; + +static const uint8_t test_exponent_buf[] = { + 0x01, 0x00, 0x01, +}; + +static const uint8_t test_modulus_buf[] = { + 0xa6, 0xf9, 0xcc, 0xed, 0x81, 0x49, 0xf0, 0x83, + 0xa7, 0xe5, 0x15, 0x93, 0xdd, 0x64, 0xa1, 0x66, + 0x40, 0xf9, 0x46, 0xd2, 0x3d, 0x16, 0xfb, 0x84, + 0x50, 0x53, 0x55, 0xba, 0x87, 0xcb, 0x15, 0xb8, + 0x98, 0xa4, 0xd2, 0xb4, 0xa6, 0xb4, 0x41, 0x2b, + 0xb4, 0x32, 0x0a, 0xc7, 0x8a, 0xe0, 0xa0, 0x2f, + 0x4e, 0xf7, 0x57, 0x44, 0xd3, 0x27, 0x94, 0x8a, + 0x10, 0x71, 0xc5, 0x3d, 0xa7, 0x25, 0xc2, 0x3f, + 0xdd, 0x1a, 0xa3, 0x05, 0x73, 0x41, 0xd8, 0x1c, + 0x99, 0xfd, 0x7f, 0x84, 0xe5, 0x09, 0xd2, 0x89, + 0x93, 0x61, 0xc6, 0xd6, 0xac, 0x6e, 0x9d, 0xe0, + 0xfb, 0x86, 0x81, 0x4a, 0x2f, 0x0a, 0xe8, 0x11, + 0xc8, 0x7f, 0x2b, 0x8b, 0x1b, 0xa3, 0x00, 0x15, + 0x25, 0xf0, 0x44, 0x39, 0xcd, 0x43, 0xbc, 0x19, + 0xad, 0xfb, 0xd3, 0xa7, 0x3f, 0x63, 0xc1, 0x78, + 0x0b, 0x69, 0x46, 0xc8, 0xe5, 0x6e, 0x15, 0x75, + 0x08, 0x8e, 0xc5, 0x67, 0xb7, 0x51, 0x61, 0x8a, + 0xb1, 0xff, 0x0a, 0xc5, 0x11, 0x30, 0x71, 0xca, + 0x88, 0x02, 0x3b, 0xfc, 0x40, 0x06, 0x11, 0x8a, + 0x0c, 0x0f, 0xb2, 0x7c, 0xf4, 0xd7, 0x07, 0xf4, + 0x85, 0xcd, 0xb7, 0x70, 0xcc, 0x5e, 0x21, 0xa0, + 0xcf, 0xb3, 0xe7, 0x47, 0x3e, 0xab, 0x20, 0x72, + 0xc3, 0xea, 0xcd, 0xfd, 0xb8, 0x50, 0x7c, 0xaa, + 0x2f, 0xd3, 0xf2, 0xc1, 0x99, 0xbc, 0x9c, 0x34, + 0xfd, 0x53, 0x26, 0x87, 0x35, 0x37, 0x78, 0xac, + 0x19, 0x13, 0x43, 0xb3, 0x80, 0x92, 0x7d, 0xeb, + 0xe2, 0x05, 0x62, 0xbf, 0x50, 0xde, 0x18, 0x17, + 0x74, 0xff, 0xa2, 0x5b, 0xfd, 0x14, 0xbf, 0xdd, + 0x21, 0xa6, 0xcb, 0xb9, 0x67, 0xce, 0x50, 0x06, + 0x91, 0x26, 0x71, 0x97, 0x5c, 0xf1, 0x81, 0x3b, + 0x68, 0xe8, 0xd9, 0xdf, 0xd3, 0xe3, 0xcc, 0x61, + 0xae, 0x73, 0x6c, 0xc8, 0x6c, 0x7b, 0x67, 0xd3, +}; + +static const struct iovec test_signature = IOVEC_MAKE(test_signature_buf, sizeof(test_signature_buf)); +static const struct iovec test_digest = IOVEC_MAKE(test_digest_buf, sizeof(test_digest_buf)); +static const struct iovec test_exponent = IOVEC_MAKE(test_exponent_buf, sizeof(test_exponent_buf)); +static const struct iovec test_modulus = IOVEC_MAKE(test_modulus_buf, sizeof(test_modulus_buf)); + +TEST(generate_rsa_test_vectors) { + /* This does not test anything but generates test vectors for dnssec_rsa_verify_raw(). + * This is skipped when we are running on valgrind or sanitizers, as it is extremely slow. */ +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) + return (void) log_tests_skipped("Running on valgrind"); +#endif +#if HAS_FEATURE_ADDRESS_SANITIZER + return (void) log_tests_skipped("Running on sanitizers"); +#endif + + const struct iovec test_message = CONST_IOVEC_MAKE_STRING("Hello world"); + + /* Generate a 2048-bit RSA key pair. */ + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *kctx = + ASSERT_NOT_NULL(sym_EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_keygen_init(kctx)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_CTX_set_rsa_keygen_bits(kctx, 2048)); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_generate(kctx, &pkey)); + + /* Export public key parameters. */ + _cleanup_(BN_freep) BIGNUM *n = NULL, *e = NULL; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e)); + _cleanup_(iovec_done) struct iovec modulus = {}, exponent = {}; + export_bn(n, &modulus); + export_bn(e, &exponent); + + /* Calculate SHA-256 digest. */ + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mctx = ASSERT_NOT_NULL(sym_EVP_MD_CTX_new()); + ASSERT_OK_POSITIVE(sym_EVP_DigestInit_ex(mctx, sym_EVP_sha256(), NULL)); + ASSERT_OK_POSITIVE(sym_EVP_DigestUpdate(mctx, test_message.iov_base, test_message.iov_len)); + struct iovec digest = IOVEC_ALLOCA(EVP_MAX_MD_SIZE); + unsigned len; + ASSERT_OK_POSITIVE(sym_EVP_DigestFinal_ex(mctx, digest.iov_base, &len)); + digest.iov_len = len; + + /* Sign the digest with RSA PKCS#1 v1.5. */ + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *sctx = ASSERT_NOT_NULL(sym_EVP_PKEY_CTX_new(pkey, NULL)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign_init(sctx)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_CTX_set_rsa_padding(sctx, RSA_PKCS1_PADDING)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_CTX_set_signature_md(sctx, sym_EVP_sha256())); + size_t sz; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign(sctx, NULL, &sz, digest.iov_base, digest.iov_len)); + struct iovec signature = IOVEC_ALLOCA(sz); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign(sctx, signature.iov_base, &signature.iov_len, digest.iov_base, digest.iov_len)); + + iovec_dump_c("test_signature_buf", &signature); + iovec_dump_c("test_digest_buf", &digest); + iovec_dump_c("test_exponent_buf", &exponent); + iovec_dump_c("test_modulus_buf", &modulus); +} + +#define TEST_RSA_VERIFY(signature, digest, exponent, modulus, expected) \ + if (expected >= 0) \ + ASSERT_OK_EQ(dnssec_rsa_verify_raw( \ + sym_EVP_sha256(), \ + &signature, \ + &digest, \ + &exponent, \ + &modulus), \ + expected); \ + else \ + ASSERT_ERROR(dnssec_rsa_verify_raw( \ + sym_EVP_sha256(), \ + &signature, \ + &digest, \ + &exponent, \ + &modulus), \ + -expected); + +TEST(dnssec_rsa_verify_raw) { + uint8_t *p; + + TEST_RSA_VERIFY(test_signature, test_digest, test_exponent, test_modulus, 1); + + _cleanup_(iovec_done) struct iovec bad_signature = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_signature, &bad_signature)); + p = bad_signature.iov_base; + p[0] ^= 0x01; + TEST_RSA_VERIFY(bad_signature, test_digest, test_exponent, test_modulus, 0); + + p[0] ^= 0x01; + bad_signature.iov_len -= 1; + TEST_RSA_VERIFY(bad_signature, test_digest, test_exponent, test_modulus, -EINVAL); + + _cleanup_(iovec_done) struct iovec bad_digest = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_digest, &bad_digest)); + p = bad_digest.iov_base; + p[0] ^= 0x01; + TEST_RSA_VERIFY(test_signature, bad_digest, test_exponent, test_modulus, 0); + + p[0] ^= 0x01; + bad_digest.iov_len -= 1; + TEST_RSA_VERIFY(test_signature, bad_digest, test_exponent, test_modulus, 0); + + _cleanup_(iovec_done) struct iovec bad_exponent = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_exponent, &bad_exponent)); + p = bad_exponent.iov_base; + p[0] ^= 0x01; + TEST_RSA_VERIFY(test_signature, test_digest, bad_exponent, test_modulus, 0); + + p[0] ^= 0x01; + bad_exponent.iov_len -= 1; + TEST_RSA_VERIFY(test_signature, test_digest, bad_exponent, test_modulus, 0); + + _cleanup_(iovec_done) struct iovec bad_modulus = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_modulus, &bad_modulus)); + p = bad_modulus.iov_base; + p[0] ^= 0x01; + TEST_RSA_VERIFY(test_signature, test_digest, test_exponent, bad_modulus, 0); + + p[0] ^= 0x01; + p[bad_modulus.iov_len - 1] ^= 0x01; + TEST_RSA_VERIFY(test_signature, test_digest, test_exponent, bad_modulus, 0); + + p[bad_modulus.iov_len - 1] ^= 0x01; + bad_modulus.iov_len -= 1; + TEST_RSA_VERIFY(test_signature, test_digest, test_exponent, bad_modulus, -EINVAL); +} + +static const uint8_t test_ecdsa_r_buf[] = { + 0x0b, 0x81, 0x39, 0x45, 0xa8, 0xcd, 0x1b, 0x12, + 0xd6, 0x9f, 0xa9, 0x88, 0xf2, 0x28, 0x39, 0x13, + 0x59, 0x1d, 0xe7, 0xee, 0xea, 0x92, 0x87, 0x0a, + 0x2c, 0x7a, 0xf2, 0x3b, 0xfb, 0x3e, 0xcb, 0x3b, +}; + +static const uint8_t test_ecdsa_s_buf[] = { + 0x74, 0xcd, 0x7c, 0x0b, 0x52, 0xa5, 0x7b, 0xf5, + 0x63, 0x84, 0xd5, 0xd9, 0xd2, 0xcb, 0xe3, 0xce, + 0x55, 0x05, 0x11, 0x7c, 0xe1, 0xfe, 0x55, 0xb4, + 0x54, 0x2e, 0x6e, 0x1c, 0xff, 0x50, 0xe5, 0x92, +}; + +static const uint8_t test_ecdsa_key_buf[] = { + 0x04, 0x23, 0x1f, 0x7d, 0x08, 0xec, 0x20, 0xcd, + 0xde, 0x03, 0x93, 0xa7, 0xb2, 0x47, 0x73, 0xeb, + 0xde, 0xd3, 0x5a, 0xbe, 0x35, 0x01, 0xda, 0x31, + 0x2b, 0x7b, 0x61, 0xcf, 0xd2, 0x30, 0xbf, 0xbf, + 0xb7, 0x6f, 0x0d, 0x86, 0x0a, 0x32, 0x46, 0xb6, + 0xdc, 0xb0, 0xd0, 0xa7, 0x3f, 0x5d, 0xdd, 0xdd, + 0xb9, 0xbb, 0x6b, 0x01, 0x61, 0x93, 0x2c, 0x1a, + 0x26, 0x65, 0x5f, 0x46, 0xb1, 0xe1, 0xc7, 0x1d, + 0x21, +}; + +static const struct iovec test_ecdsa_r = IOVEC_MAKE(test_ecdsa_r_buf, sizeof(test_ecdsa_r_buf)); +static const struct iovec test_ecdsa_s = IOVEC_MAKE(test_ecdsa_s_buf, sizeof(test_ecdsa_s_buf)); +static const struct iovec test_ecdsa_key = IOVEC_MAKE(test_ecdsa_key_buf, sizeof(test_ecdsa_key_buf)); + +TEST(generate_ecdsa_test_vectors) { + /* This does not test anything but generates test vectors for dnssec_ecdsa_verify_raw(). + * This is skipped when we are running on valgrind or sanitizers, as it is extremely slow. */ +#if HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) + return (void) log_tests_skipped("Running on valgrind"); +#endif +#if HAS_FEATURE_ADDRESS_SANITIZER + return (void) log_tests_skipped("Running on sanitizers"); +#endif + + /* Generate a P-256 (prime256v1) EC key pair. */ + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *kctx = + ASSERT_NOT_NULL(sym_EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_keygen_init(kctx)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_CTX_set_ec_paramgen_curve_nid(kctx, NID_X9_62_prime256v1)); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_generate(kctx, &pkey)); + + /* Export uncompressed public key point (0x04 || X || Y) */ + size_t key_sz = 0; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, NULL, 0, &key_sz)); + struct iovec pubkey = IOVEC_ALLOCA(key_sz); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, pubkey.iov_base, key_sz, &key_sz)); + pubkey.iov_len = key_sz; + + /* Sign the existing test_digest with ECDSA */ + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *sctx = ASSERT_NOT_NULL(sym_EVP_PKEY_CTX_new(pkey, NULL)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign_init(sctx)); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_CTX_set_signature_md(sctx, sym_EVP_sha256())); + size_t sig_sz; + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign(sctx, NULL, &sig_sz, test_digest.iov_base, test_digest.iov_len)); + struct iovec signature_der = IOVEC_ALLOCA(sig_sz); + ASSERT_OK_POSITIVE(sym_EVP_PKEY_sign(sctx, signature_der.iov_base, &signature_der.iov_len, test_digest.iov_base, test_digest.iov_len)); + + /* ECDSA signature is DER encoded. Extract r and s components. */ + const uint8_t *p = signature_der.iov_base; + _cleanup_(ECDSA_SIG_freep) ECDSA_SIG *esig = ASSERT_NOT_NULL(sym_d2i_ECDSA_SIG(NULL, &p, signature_der.iov_len)); + + const BIGNUM *r_bn = sym_ECDSA_SIG_get0_r(esig); + const BIGNUM *s_bn = sym_ECDSA_SIG_get0_s(esig); + + _cleanup_(iovec_done) struct iovec r_iov = {}, s_iov = {}; + export_bn(r_bn, &r_iov); + export_bn(s_bn, &s_iov); + + iovec_dump_c("test_ecdsa_r_buf", &r_iov); + iovec_dump_c("test_ecdsa_s_buf", &s_iov); + iovec_dump_c("test_ecdsa_key_buf", &pubkey); +} + +#define TEST_ECDSA_VERIFY(r, s, digest, key, expected) \ + if (expected >= 0) \ + ASSERT_OK_EQ(dnssec_ecdsa_verify_raw( \ + sym_EVP_sha256(), \ + NID_X9_62_prime256v1, \ + &r, &s, &digest, &key), \ + expected); \ + else \ + ASSERT_ERROR(dnssec_ecdsa_verify_raw( \ + sym_EVP_sha256(), \ + NID_X9_62_prime256v1, \ + &r, &s, &digest, &key), \ + -expected); + +TEST(dnssec_ecdsa_verify_raw) { + uint8_t *p; + + /* Normal verification */ + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, test_digest, test_ecdsa_key, 1); + + /* Fuzzing R component */ + _cleanup_(iovec_done) struct iovec bad_r = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_ecdsa_r, &bad_r)); + p = bad_r.iov_base; + p[0] ^= 0x01; + TEST_ECDSA_VERIFY(bad_r, test_ecdsa_s, test_digest, test_ecdsa_key, 0); + + p[0] ^= 0x01; + bad_r.iov_len -= 1; + TEST_ECDSA_VERIFY(bad_r, test_ecdsa_s, test_digest, test_ecdsa_key, 0); + + /* Fuzzing S component */ + _cleanup_(iovec_done) struct iovec bad_s = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_ecdsa_s, &bad_s)); + p = bad_s.iov_base; + p[0] ^= 0x01; + TEST_ECDSA_VERIFY(test_ecdsa_r, bad_s, test_digest, test_ecdsa_key, 0); + + p[0] ^= 0x01; + bad_s.iov_len -= 1; + TEST_ECDSA_VERIFY(test_ecdsa_r, bad_s, test_digest, test_ecdsa_key, 0); + + /* Fuzzing Digest */ + _cleanup_(iovec_done) struct iovec bad_digest = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_digest, &bad_digest)); + p = bad_digest.iov_base; + p[0] ^= 0x01; + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, bad_digest, test_ecdsa_key, 0); + + p[0] ^= 0x01; + bad_digest.iov_len -= 1; + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, bad_digest, test_ecdsa_key, 0); + + /* Fuzzing Public Key Point */ + _cleanup_(iovec_done) struct iovec bad_key = {}; + ASSERT_NOT_NULL(iovec_memdup(&test_ecdsa_key, &bad_key)); + p = bad_key.iov_base; + p[bad_key.iov_len - 1] ^= 0x01; + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, test_digest, bad_key, -ENOTRECOVERABLE); + + p[bad_key.iov_len - 1] ^= 0x01; + bad_key.iov_len -= 1; + TEST_ECDSA_VERIFY(test_ecdsa_r, test_ecdsa_s, test_digest, bad_key, -ENOTRECOVERABLE); +} + +static int intro(void) { + if (DLOPEN_LIBCRYPTO(LOG_DEBUG, SD_ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED) < 0) + return EXIT_TEST_SKIP; + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/run/run.c b/src/run/run.c index 1fca68c76f84b..068e0cc29feaf 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -552,19 +552,24 @@ static int parse_argv(int argc, char *argv[]) { OPTION_LONG("on-calendar", "SPEC", "Realtime timer"): { _cleanup_(calendar_spec_freep) CalendarSpec *cs = NULL; - r = calendar_spec_from_string(opts.arg, &cs); + r = calendar_spec_from_string_full(opts.arg, &cs, /* warn_on_weekday_mismatch= */ true); if (r < 0) return log_error_errno(r, "Failed to parse calendar event specification: %m"); /* Let's make sure the given calendar event is not in the past */ r = calendar_spec_next_usec(cs, now(CLOCK_REALTIME), NULL); - if (r == -ENOENT) - /* The calendar event is in the past — let's warn about this, but install it - * anyway as is. The service manager will trigger the service right away. - * Moreover, the server side might have a different clock or timezone than we - * do, hence it should decide when or whether to run something. */ - log_warning("Specified calendar expression is in the past, proceeding anyway."); - else if (r < 0) + if (r == -ENOENT) { + /* The calendar event might be in the past, so let's warn about this, but + * install it anyway as is. The service manager will trigger the service + * right away. Moreover, the server side might have a different clock or + * timezone than we do, hence it should decide when or whether to run + * something. + * + * However, a mismatching weekday for a fixed date also results in -ENOENT, + * and was already warned about when parsing. */ + if (!calendar_spec_weekday_conflicts(cs, NULL)) + log_warning("Specified calendar expression is in the past, proceeding anyway."); + } else if (r < 0) return log_error_errno(r, "Failed to calculate next time calendar expression elapses: %m"); r = add_timer_property("OnCalendar", opts.arg); diff --git a/src/sbsign/authenticode.h b/src/sbsign/authenticode.h index c076fe36241d8..236435a7e96f6 100644 --- a/src/sbsign/authenticode.h +++ b/src/sbsign/authenticode.h @@ -1,6 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#ifndef SYSTEMD_CFLAGS_MARKER_LIBOPENSSL +# error "missing libopenssl_cflags in meson dependency." +#endif + #include #include "shared-forward.h" diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h index 981f43a8cc082..2b9ac81736653 100644 --- a/src/shared/acl-util.h +++ b/src/shared/acl-util.h @@ -6,6 +6,10 @@ #include "shared-forward.h" #if HAVE_ACL +#ifndef SYSTEMD_CFLAGS_MARKER_LIBACL +# error "missing libacl_cflags in meson dependency." +#endif + #include /* IWYU pragma: export */ #include /* IWYU pragma: export */ diff --git a/src/shared/apparmor-util.h b/src/shared/apparmor-util.h index e87ba84504d7d..2d47a341cb9be 100644 --- a/src/shared/apparmor-util.h +++ b/src/shared/apparmor-util.h @@ -4,6 +4,10 @@ #include "shared-forward.h" #if HAVE_APPARMOR +# ifndef SYSTEMD_CFLAGS_MARKER_LIBAPPARMOR +# error "missing libapparmor_cflags in meson dependency." +# endif + # include # include "dlfcn-util.h" diff --git a/src/shared/blkid-util.h b/src/shared/blkid-util.h index c211bbf3abc0f..97356477fadfa 100644 --- a/src/shared/blkid-util.h +++ b/src/shared/blkid-util.h @@ -6,6 +6,9 @@ #include "shared-forward.h" #if HAVE_BLKID +# ifndef SYSTEMD_CFLAGS_MARKER_LIBBLKID +# error "missing libblkid_cflags in meson dependency." +# endif #include diff --git a/src/shared/bpf-link.h b/src/shared/bpf-link.h index 4de95eb2e1ee3..b5be07865d391 100644 --- a/src/shared/bpf-link.h +++ b/src/shared/bpf-link.h @@ -2,6 +2,9 @@ #pragma once #if HAVE_LIBBPF +#ifndef SYSTEMD_CFLAGS_MARKER_LIBBPF +# error "missing libbpf_cflags in meson dependency." +#endif #include diff --git a/src/shared/bpf-util.h b/src/shared/bpf-util.h index faf7349b8a079..6c2e7a2c98e43 100644 --- a/src/shared/bpf-util.h +++ b/src/shared/bpf-util.h @@ -6,6 +6,9 @@ #include "sd-dlopen.h" #if HAVE_LIBBPF +#ifndef SYSTEMD_CFLAGS_MARKER_LIBBPF +# error "missing libbpf_cflags in meson dependency." +#endif #include /* IWYU pragma: export */ #include /* IWYU pragma: export */ diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index 225d811d19280..6a56438c1c7ba 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -15,7 +15,7 @@ #include "strv.h" #include "time-util.h" -#define BITS_WEEKDAYS 127 +#define BITS_WEEKDAYS ((1 << _WEEKDAY_MAX) - 1) #define MIN_YEAR 1970 #define MAX_YEAR 2199 @@ -237,16 +237,6 @@ _pure_ bool calendar_spec_valid(CalendarSpec *c) { } static void format_weekdays(FILE *f, const CalendarSpec *c) { - static const char *const days[] = { - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun", - }; - int l, x; bool need_comma = false; @@ -254,7 +244,7 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) { assert(c); assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS); - for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) { + for (x = 0, l = -1; x < _WEEKDAY_MAX; x++) { if (c->weekdays_bits & (1 << x)) { @@ -264,7 +254,7 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) { else need_comma = true; - fputs(days[x], f); + fputs(weekday_to_string(x), f); l = x; } @@ -272,7 +262,7 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) { if (x > l + 1) { fputs(x > l + 2 ? ".." : ",", f); - fputs(days[x-1], f); + fputs(weekday_to_string(x-1), f); } l = -1; @@ -281,7 +271,7 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) { if (l >= 0 && x > l + 1) { fputs(x > l + 2 ? ".." : ",", f); - fputs(days[x-1], f); + fputs(weekday_to_string(x-1), f); } } @@ -877,7 +867,7 @@ static int parse_calendar_time(const char **p, CalendarSpec *c) { return 0; } -int calendar_spec_from_string(const char *p, CalendarSpec **ret) { +int calendar_spec_from_string_full(const char *p, CalendarSpec **ret, bool warn_on_weekday_mismatch) { const char *utc; _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL; _cleanup_free_ char *p_tmp = NULL; @@ -1098,6 +1088,15 @@ int calendar_spec_from_string(const char *p, CalendarSpec **ret) { if (!calendar_spec_valid(c)) return -EINVAL; + if (warn_on_weekday_mismatch) { + int wday; + if (calendar_spec_weekday_conflicts(c, &wday)) + log_warning("Weekday constraint does not match the fixed date %04d-%02d-%02d " + "(which is a %s), so this timer will never elapse.", + c->year->start, c->month->start, c->day->start, + weekday_to_string(wday)); + } + if (ret) *ret = TAKE_PTR(c); return 0; @@ -1228,6 +1227,7 @@ static int tm_within_bounds(struct tm *tm, bool utc) { static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { struct tm t; + usec_t usec; int k; assert(tm); @@ -1236,13 +1236,64 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { return true; t = *tm; - if (mktime_or_timegm_usec(&t, utc, /* ret= */ NULL) < 0) + if (mktime_or_timegm_usec(&t, utc, &usec) < 0) + return false; + if (localtime_or_gmtime_usec(usec, utc, &t) < 0) return false; k = t.tm_wday == 0 ? 6 : t.tm_wday - 1; return (weekdays_bits & (1 << k)); } +static bool component_is_single_fixed(const CalendarComponent *c) { + /* Returns true if the component is a single fixed value: no range, no repeat, no alternatives. */ + return c && !c->next && c->repeat == 0 && (c->stop < 0 || c->stop == c->start); +} + +bool calendar_spec_weekday_conflicts(const CalendarSpec *spec, int *ret_actual_wday) { + assert(spec); + + if (spec->weekdays_bits <= 0 || spec->weekdays_bits >= BITS_WEEKDAYS) + return false; + + /* Skip when end_of_month (~) is used: day->start holds an offset, not an + * absolute day-of-month, so the real firing date cannot be determined here. */ + if (spec->end_of_month) + return false; + + /* Only detectable when year, month and day are each a single fixed value */ + if (!component_is_single_fixed(spec->year) || + !component_is_single_fixed(spec->month) || + !component_is_single_fixed(spec->day)) + return false; + + /* CalendarSpec stores month as 1-12; struct tm uses 0-11 */ + struct tm tm = { + .tm_year = spec->year->start - 1900, + .tm_mon = spec->month->start - 1, + .tm_mday = spec->day->start, + }; + struct tm orig = tm; + + /* Compute weekday in UTC to avoid TZ/DST skew; reject dates that would be + * silently normalised (e.g. 2027-02-31 → 2027-03-03). + * + * Do not rely on timegm() to fill tm_wday, since musl does not do that. */ + usec_t usec; + if (mktime_or_timegm_usec(&tm, /* utc= */ true, &usec) < 0 || + localtime_or_gmtime_usec(usec, /* utc= */ true, &tm) < 0 || + tm.tm_year != orig.tm_year || tm.tm_mon != orig.tm_mon || + tm.tm_mday != orig.tm_mday) + return false; + + if (matches_weekday(spec->weekdays_bits, &tm, /* utc= */ true)) + return false; /* no conflict */ + + if (ret_actual_wday) + *ret_actual_wday = tm.tm_wday == 0 ? 6 : tm.tm_wday - 1; + return true; +} + static int tm_compare(const struct tm *t1, const struct tm *t2) { int r; diff --git a/src/shared/calendarspec.h b/src/shared/calendarspec.h index ce76a7c1c6240..339dda30abea6 100644 --- a/src/shared/calendarspec.h +++ b/src/shared/calendarspec.h @@ -35,8 +35,13 @@ CalendarSpec* calendar_spec_free(CalendarSpec *c); bool calendar_spec_valid(CalendarSpec *spec); int calendar_spec_to_string(const CalendarSpec *spec, char **ret); -int calendar_spec_from_string(const char *p, CalendarSpec **ret); +int calendar_spec_from_string_full(const char *p, CalendarSpec **ret, bool warn_on_weekday_mismatch); + +static inline int calendar_spec_from_string(const char *p, CalendarSpec **ret) { + return calendar_spec_from_string_full(p, ret, /* warn_on_weekday_mismatch= */ false); +} int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next); +bool calendar_spec_weekday_conflicts(const CalendarSpec *spec, int *ret_actual_wday); DEFINE_TRIVIAL_CLEANUP_FUNC(CalendarSpec*, calendar_spec_free); diff --git a/src/shared/crypto-util.c b/src/shared/crypto-util.c index 57da78b00355e..d791996efc7a2 100644 --- a/src/shared/crypto-util.c +++ b/src/shared/crypto-util.c @@ -31,9 +31,7 @@ struct OpenSSLAskPasswordUI { AskPasswordRequest request; -#ifndef OPENSSL_NO_UI_CONSOLE UI_METHOD *method; -#endif }; DLSYM_PROTOTYPE(ASN1_ANY_it) = NULL; @@ -100,6 +98,10 @@ DLSYM_PROTOTYPE(BN_set_word) = NULL; DLSYM_PROTOTYPE(BN_sub_word) = NULL; DLSYM_PROTOTYPE(CRYPTO_free) = NULL; DLSYM_PROTOTYPE(ECDSA_SIG_free) = NULL; +DLSYM_PROTOTYPE(ECDSA_SIG_get0_r) = NULL; +DLSYM_PROTOTYPE(ECDSA_SIG_get0_s) = NULL; +DLSYM_PROTOTYPE(ECDSA_SIG_new) = NULL; +DLSYM_PROTOTYPE(ECDSA_SIG_set0) = NULL; DLSYM_PROTOTYPE(EC_GROUP_free) = NULL; DLSYM_PROTOTYPE(EC_GROUP_get0_generator) = NULL; DLSYM_PROTOTYPE(EC_GROUP_get0_order) = NULL; @@ -170,7 +172,8 @@ DLSYM_PROTOTYPE(EVP_PKEY_CTX_new) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_from_name) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_id) = NULL; static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set0_rsa_oaep_label) = NULL; -static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_ec_paramgen_curve_nid) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_ec_paramgen_curve_nid) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_keygen_bits) = NULL; static DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_oaep_md) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_padding) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_signature_md) = NULL; @@ -183,17 +186,23 @@ DLSYM_PROTOTYPE(EVP_PKEY_eq) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_free) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_fromdata) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_generate) = NULL; static DLSYM_PROTOTYPE(EVP_PKEY_get1_encoded_public_key) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_get_base_id) = NULL; static DLSYM_PROTOTYPE(EVP_PKEY_get_bits) = NULL; -static DLSYM_PROTOTYPE(EVP_PKEY_get_bn_param) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_get_bn_param) = NULL; static DLSYM_PROTOTYPE(EVP_PKEY_get_group_name) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_get_id) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_get_octet_string_param) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_get_size) = NULL; static DLSYM_PROTOTYPE(EVP_PKEY_get_utf8_string_param) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_keygen) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_keygen_init) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_new) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_new_raw_public_key) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_public_check) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_sign) = NULL; +DLSYM_PROTOTYPE(EVP_PKEY_sign_init) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_verify) = NULL; DLSYM_PROTOTYPE(EVP_PKEY_verify_init) = NULL; DLSYM_PROTOTYPE(EVP_aes_256_ctr) = NULL; @@ -217,6 +226,7 @@ DLSYM_PROTOTYPE(OPENSSL_sk_value) = NULL; DLSYM_PROTOTYPE(OSSL_EC_curve_nid2name) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_BLD_free) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_BLD_new) = NULL; +DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_BN) = NULL; static DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_octet_string) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_utf8_string) = NULL; DLSYM_PROTOTYPE(OSSL_PARAM_BLD_to_param) = NULL; @@ -264,7 +274,6 @@ DLSYM_PROTOTYPE(PKCS7_set_content) = NULL; static DLSYM_PROTOTYPE(PKCS7_set_type) = NULL; DLSYM_PROTOTYPE(PKCS7_sign) = NULL; DLSYM_PROTOTYPE(PKCS7_verify) = NULL; -DLSYM_PROTOTYPE(SHA1) = NULL; DLSYM_PROTOTYPE(SHA512) = NULL; DLSYM_PROTOTYPE(X509_ALGOR_free) = NULL; static DLSYM_PROTOTYPE(X509_ALGOR_set0) = NULL; @@ -284,15 +293,16 @@ static DLSYM_PROTOTYPE(X509_get_signature_info) = NULL; DLSYM_PROTOTYPE(X509_get_subject_name) = NULL; DLSYM_PROTOTYPE(X509_gmtime_adj) = NULL; DLSYM_PROTOTYPE(d2i_ASN1_OCTET_STRING) = NULL; +DLSYM_PROTOTYPE(d2i_ECDSA_SIG) = NULL; DLSYM_PROTOTYPE(d2i_ECPKParameters) = NULL; DLSYM_PROTOTYPE(d2i_PKCS7) = NULL; DLSYM_PROTOTYPE(d2i_PUBKEY) = NULL; DLSYM_PROTOTYPE(d2i_X509) = NULL; DLSYM_PROTOTYPE(i2d_ASN1_INTEGER) = NULL; +DLSYM_PROTOTYPE(i2d_ECDSA_SIG) = NULL; DLSYM_PROTOTYPE(i2d_PKCS7) = NULL; DLSYM_PROTOTYPE(i2d_PKCS7_fp) = NULL; DLSYM_PROTOTYPE(i2d_PUBKEY) = NULL; -static DLSYM_PROTOTYPE(i2d_PUBKEY_fp) = NULL; static DLSYM_PROTOTYPE(i2d_PublicKey) = NULL; DLSYM_PROTOTYPE(i2d_X509) = NULL; DLSYM_PROTOTYPE(i2d_X509_NAME) = NULL; @@ -313,25 +323,6 @@ REENABLE_WARNING; DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(ENGINE*, sym_ENGINE_free, ENGINE_freep, NULL); #endif -#if !defined(OPENSSL_NO_DEPRECATED_3_0) -DISABLE_WARNING_DEPRECATED_DECLARATIONS; -DLSYM_PROTOTYPE(ECDSA_SIG_new) = NULL; -DLSYM_PROTOTYPE(ECDSA_SIG_set0) = NULL; -DLSYM_PROTOTYPE(ECDSA_do_verify) = NULL; -DLSYM_PROTOTYPE(EC_KEY_check_key) = NULL; -DLSYM_PROTOTYPE(EC_KEY_free) = NULL; -DLSYM_PROTOTYPE(EC_KEY_new) = NULL; -DLSYM_PROTOTYPE(EC_KEY_set_group) = NULL; -DLSYM_PROTOTYPE(EC_KEY_set_public_key) = NULL; -DLSYM_PROTOTYPE(EVP_PKEY_assign) = NULL; -DLSYM_PROTOTYPE(RSAPublicKey_dup) = NULL; -DLSYM_PROTOTYPE(RSA_free) = NULL; -DLSYM_PROTOTYPE(RSA_new) = NULL; -DLSYM_PROTOTYPE(RSA_set0_key) = NULL; -DLSYM_PROTOTYPE(RSA_size) = NULL; -REENABLE_WARNING; -#endif - #ifndef OPENSSL_NO_UI_CONSOLE static DLSYM_PROTOTYPE(UI_OpenSSL) = NULL; static DLSYM_PROTOTYPE(UI_create_method) = NULL; @@ -428,6 +419,10 @@ int dlopen_libcrypto(int log_level) { DLSYM_ARG(BN_sub_word), DLSYM_ARG(CRYPTO_free), DLSYM_ARG(ECDSA_SIG_free), + DLSYM_ARG(ECDSA_SIG_get0_r), + DLSYM_ARG(ECDSA_SIG_get0_s), + DLSYM_ARG(ECDSA_SIG_new), + DLSYM_ARG(ECDSA_SIG_set0), DLSYM_ARG(EC_GROUP_free), DLSYM_ARG(EC_GROUP_get0_generator), DLSYM_ARG(EC_GROUP_get0_order), @@ -499,6 +494,7 @@ int dlopen_libcrypto(int log_level) { DLSYM_ARG(EVP_PKEY_CTX_new_id), DLSYM_ARG(EVP_PKEY_CTX_set0_rsa_oaep_label), DLSYM_ARG(EVP_PKEY_CTX_set_ec_paramgen_curve_nid), + DLSYM_ARG(EVP_PKEY_CTX_set_rsa_keygen_bits), DLSYM_ARG(EVP_PKEY_CTX_set_rsa_oaep_md), DLSYM_ARG(EVP_PKEY_CTX_set_rsa_padding), DLSYM_ARG(EVP_PKEY_CTX_set_signature_md), @@ -511,17 +507,23 @@ int dlopen_libcrypto(int log_level) { DLSYM_ARG(EVP_PKEY_free), DLSYM_ARG(EVP_PKEY_fromdata), DLSYM_ARG(EVP_PKEY_fromdata_init), + DLSYM_ARG(EVP_PKEY_generate), DLSYM_ARG(EVP_PKEY_get1_encoded_public_key), DLSYM_ARG(EVP_PKEY_get_base_id), DLSYM_ARG(EVP_PKEY_get_bits), DLSYM_ARG(EVP_PKEY_get_bn_param), DLSYM_ARG(EVP_PKEY_get_group_name), DLSYM_ARG(EVP_PKEY_get_id), + DLSYM_ARG(EVP_PKEY_get_octet_string_param), + DLSYM_ARG(EVP_PKEY_get_size), DLSYM_ARG(EVP_PKEY_get_utf8_string_param), DLSYM_ARG(EVP_PKEY_keygen), DLSYM_ARG(EVP_PKEY_keygen_init), DLSYM_ARG(EVP_PKEY_new), DLSYM_ARG(EVP_PKEY_new_raw_public_key), + DLSYM_ARG(EVP_PKEY_public_check), + DLSYM_ARG(EVP_PKEY_sign), + DLSYM_ARG(EVP_PKEY_sign_init), DLSYM_ARG(EVP_PKEY_verify), DLSYM_ARG(EVP_PKEY_verify_init), DLSYM_ARG(EVP_aes_256_ctr), @@ -545,6 +547,7 @@ int dlopen_libcrypto(int log_level) { DLSYM_ARG(OSSL_EC_curve_nid2name), DLSYM_ARG(OSSL_PARAM_BLD_free), DLSYM_ARG(OSSL_PARAM_BLD_new), + DLSYM_ARG(OSSL_PARAM_BLD_push_BN), DLSYM_ARG(OSSL_PARAM_BLD_push_octet_string), DLSYM_ARG(OSSL_PARAM_BLD_push_utf8_string), DLSYM_ARG(OSSL_PARAM_BLD_to_param), @@ -592,7 +595,6 @@ int dlopen_libcrypto(int log_level) { DLSYM_ARG(PKCS7_set_type), DLSYM_ARG(PKCS7_sign), DLSYM_ARG(PKCS7_verify), - DLSYM_ARG(SHA1), DLSYM_ARG(SHA512), DLSYM_ARG(X509_ALGOR_free), DLSYM_ARG(X509_ALGOR_set0), @@ -612,56 +614,19 @@ int dlopen_libcrypto(int log_level) { DLSYM_ARG(X509_get_subject_name), DLSYM_ARG(X509_gmtime_adj), DLSYM_ARG(d2i_ASN1_OCTET_STRING), + DLSYM_ARG(d2i_ECDSA_SIG), DLSYM_ARG(d2i_ECPKParameters), DLSYM_ARG(d2i_PKCS7), DLSYM_ARG(d2i_PUBKEY), DLSYM_ARG(d2i_X509), DLSYM_ARG(i2d_ASN1_INTEGER), + DLSYM_ARG(i2d_ECDSA_SIG), DLSYM_ARG(i2d_PKCS7), DLSYM_ARG(i2d_PKCS7_fp), DLSYM_ARG(i2d_PUBKEY), - DLSYM_ARG(i2d_PUBKEY_fp), DLSYM_ARG(i2d_PublicKey), DLSYM_ARG(i2d_X509), - DLSYM_ARG(i2d_X509_NAME), -#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) - DLSYM_ARG_FORCE(ENGINE_by_id), - DLSYM_ARG_FORCE(ENGINE_free), - DLSYM_ARG_FORCE(ENGINE_init), - DLSYM_ARG_FORCE(ENGINE_load_private_key), -#endif -#if !defined(OPENSSL_NO_DEPRECATED_3_0) - DLSYM_ARG_FORCE(ECDSA_SIG_new), - DLSYM_ARG_FORCE(ECDSA_SIG_set0), - DLSYM_ARG_FORCE(ECDSA_do_verify), - DLSYM_ARG_FORCE(EC_KEY_check_key), - DLSYM_ARG_FORCE(EC_KEY_free), - DLSYM_ARG_FORCE(EC_KEY_new), - DLSYM_ARG_FORCE(EC_KEY_set_group), - DLSYM_ARG_FORCE(EC_KEY_set_public_key), - DLSYM_ARG_FORCE(EVP_PKEY_assign), - DLSYM_ARG_FORCE(RSAPublicKey_dup), - DLSYM_ARG_FORCE(RSA_free), - DLSYM_ARG_FORCE(RSA_new), - DLSYM_ARG_FORCE(RSA_set0_key), - DLSYM_ARG_FORCE(RSA_size), -#endif -#ifndef OPENSSL_NO_UI_CONSOLE - DLSYM_ARG(UI_OpenSSL), - DLSYM_ARG(UI_create_method), - DLSYM_ARG(UI_destroy_method), - DLSYM_ARG(UI_get0_output_string), - DLSYM_ARG(UI_get_default_method), - DLSYM_ARG(UI_get_method), - DLSYM_ARG(UI_get_string_type), - DLSYM_ARG(UI_method_get_ex_data), - DLSYM_ARG(UI_method_get_reader), - DLSYM_ARG(UI_method_set_ex_data), - DLSYM_ARG(UI_method_set_reader), - DLSYM_ARG(UI_set_default_method), - DLSYM_ARG(UI_set_result), -#endif - NULL); + DLSYM_ARG(i2d_X509_NAME)); if (r >= 0) break; } @@ -670,6 +635,33 @@ int dlopen_libcrypto(int log_level) { return -EOPNOTSUPP; /* turn into recognizable error */ } +#ifndef OPENSSL_NO_UI_CONSOLE + /* Load UI API optionally so we don't fail to load libcrypto.so lacking UI support, + * even if systemd is built with UI support enabled in the headers. */ + DLSYM_OPTIONAL(libcrypto_dl, UI_OpenSSL); + DLSYM_OPTIONAL(libcrypto_dl, UI_create_method); + DLSYM_OPTIONAL(libcrypto_dl, UI_destroy_method); + DLSYM_OPTIONAL(libcrypto_dl, UI_get0_output_string); + DLSYM_OPTIONAL(libcrypto_dl, UI_get_default_method); + DLSYM_OPTIONAL(libcrypto_dl, UI_get_method); + DLSYM_OPTIONAL(libcrypto_dl, UI_get_string_type); + DLSYM_OPTIONAL(libcrypto_dl, UI_method_get_ex_data); + DLSYM_OPTIONAL(libcrypto_dl, UI_method_get_reader); + DLSYM_OPTIONAL(libcrypto_dl, UI_method_set_ex_data); + DLSYM_OPTIONAL(libcrypto_dl, UI_method_set_reader); + DLSYM_OPTIONAL(libcrypto_dl, UI_set_default_method); + DLSYM_OPTIONAL(libcrypto_dl, UI_set_result); +#endif + +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + /* Load ENGINE API optionally so we don't fail when loading libcrypto.so.4 even if systemd is built + * with openssl-3 headers. */ + DLSYM_OPTIONAL(libcrypto_dl, ENGINE_by_id); + DLSYM_OPTIONAL(libcrypto_dl, ENGINE_init); + DLSYM_OPTIONAL(libcrypto_dl, ENGINE_free); + DLSYM_OPTIONAL(libcrypto_dl, ENGINE_load_private_key); +#endif + return r; #else return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -1359,30 +1351,32 @@ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size if (sym_EVP_PKEY_fromdata_init(ctx) <= 0) return log_openssl_errors(LOG_DEBUG, "Failed to initialize EVP_PKEY_CTX"); - OSSL_PARAM params[3]; + _cleanup_(BN_freep) BIGNUM *bn_n = sym_BN_bin2bn(n, n_size, NULL); + if (!bn_n) + return log_openssl_errors(LOG_DEBUG, "Failed to create BIGNUM n"); -#if __BYTE_ORDER == __BIG_ENDIAN - params[0] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size); - params[1] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size); -#else - _cleanup_free_ void *native_n = memdup_reverse(n, n_size); - if (!native_n) - return log_oom_debug(); + _cleanup_(BN_freep) BIGNUM *bn_e = sym_BN_bin2bn(e, e_size, NULL); + if (!bn_e) + return log_openssl_errors(LOG_DEBUG, "Failed to create BIGNUM e"); - _cleanup_free_ void *native_e = memdup_reverse(e, e_size); - if (!native_e) - return log_oom_debug(); + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = sym_OSSL_PARAM_BLD_new(); + if (!bld) + return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD"); - params[0] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size); - params[1] = sym_OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size); -#endif - params[2] = sym_OSSL_PARAM_construct_end(); + if (!sym_OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, bn_n)) + return log_openssl_errors(LOG_DEBUG, "Failed to push n to RSA params"); + + if (!sym_OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, bn_e)) + return log_openssl_errors(LOG_DEBUG, "Failed to push e to RSA params"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld); + if (!params) + return log_openssl_errors(LOG_DEBUG, "Failed to build RSA OSSL_PARAM"); if (sym_EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) return log_openssl_errors(LOG_DEBUG, "Failed to create RSA EVP_PKEY"); *ret = TAKE_PTR(pkey); - return 0; } @@ -1642,10 +1636,9 @@ int ecc_ecdh(const EVP_PKEY *private_pkey, int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) { _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL; - _cleanup_free_ void *d = NULL, *h = NULL; - int sz, lsz, msz; + _cleanup_free_ void *h = NULL; + int lsz, msz; unsigned umsz; - unsigned char *dd; int r; /* Calculates a message digest of the DER encoded public key */ @@ -1659,15 +1652,8 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s if (r < 0) return r; - sz = sym_i2d_PublicKey(pk, NULL); - if (sz < 0) - return log_openssl_errors(LOG_DEBUG, "Unable to convert public key to DER format"); - - dd = d = malloc(sz); - if (!d) - return log_oom_debug(); - - lsz = sym_i2d_PublicKey(pk, &dd); + _cleanup_(OPENSSL_freep) void *d = NULL; + lsz = sym_i2d_PublicKey(pk, (unsigned char**) &d); if (lsz < 0) return log_openssl_errors(LOG_DEBUG, "Unable to convert public key to DER format"); @@ -1875,17 +1861,12 @@ static int ecc_pkey_generate_volume_keys( _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL; _cleanup_(erase_and_freep) void *decrypted_key = NULL; - _cleanup_free_ unsigned char *saved_key = NULL; size_t decrypted_key_size, saved_key_size; int r; _cleanup_free_ char *curve_name = NULL; size_t len = 0; - r = dlopen_libcrypto(LOG_DEBUG); - if (r < 0) - return r; - if (sym_EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0) return log_openssl_errors(LOG_DEBUG, "Failed to determine PKEY group name length"); @@ -1907,10 +1888,17 @@ static int ecc_pkey_generate_volume_keys( /* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points. See https://github.com/openssl/openssl/discussions/22835 */ - saved_key_size = sym_EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key); + _cleanup_(OPENSSL_freep) void *buf = NULL; + saved_key_size = sym_EVP_PKEY_get1_encoded_public_key(pkey_new, (unsigned char**) &buf); if (saved_key_size == 0) return log_openssl_errors(LOG_DEBUG, "Failed to convert the generated public key to SEC1 format"); + /* 'buf' is allocated by OpenSSL and must be freed via OPENSSL_free(). We duplicate it here so the + * caller can safely use standard free(). */ + _cleanup_free_ void *saved_key = memdup(buf, saved_key_size); + if (!saved_key) + return log_oom_debug(); + *ret_decrypted_key = TAKE_PTR(decrypted_key); *ret_decrypted_key_size = decrypted_key_size; *ret_saved_key = TAKE_PTR(saved_key); @@ -2007,19 +1995,13 @@ int pkey_generate_volume_keys( static int load_key_from_provider( const char *provider, const char *private_key_uri, - UI_METHOD *ui_method, + UI_METHOD *ui_method, /* can be NULL */ EVP_PKEY **ret) { - int r; - assert(provider); assert(private_key_uri); assert(ret); - r = dlopen_libcrypto(LOG_DEBUG); - if (r < 0) - return r; - /* Load the provider so that this can work without any custom written configuration in /etc/. * Also load the 'default' as that seems to be the recommendation. */ if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) @@ -2052,21 +2034,25 @@ static int load_key_from_provider( return 0; } -static int load_key_from_engine(const char *engine, const char *private_key_uri, UI_METHOD *ui_method, EVP_PKEY **ret) { -#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) - int r; -#endif +static int load_key_from_engine( + const char *engine, + const char *private_key_uri, + UI_METHOD *ui_method, /* can be NULL */ + EVP_PKEY **ret) { +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) assert(engine); assert(private_key_uri); assert(ret); -#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) - r = dlopen_libcrypto(LOG_DEBUG); - if (r < 0) - return r; - DISABLE_WARNING_DEPRECATED_DECLARATIONS; + if (!sym_ENGINE_by_id || + !sym_ENGINE_free || + !sym_ENGINE_init || + !sym_ENGINE_load_private_key) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "ENGINE API is not available in the loaded OpenSSL library."); + _cleanup_(ENGINE_freep) ENGINE *e = sym_ENGINE_by_id(engine); if (!e) return log_openssl_errors(LOG_DEBUG, "Failed to load signing engine '%s'", engine); @@ -2087,11 +2073,79 @@ static int load_key_from_engine(const char *engine, const char *private_key_uri, #endif } +static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) { + _cleanup_(erase_and_freep) char *rawkey = NULL; + _cleanup_(BIO_freep) BIO *kb = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL; + size_t rawkeysz; + int r; + + assert(path); + assert(ret); + + r = read_full_file_full( + AT_FDCWD, path, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &rawkey, &rawkeysz); + if (r < 0) + return log_debug_errno(r, "Failed to read key file '%s': %m", path); + + kb = sym_BIO_new_mem_buf(rawkey, rawkeysz); + if (!kb) + return log_oom_debug(); + + pk = sym_PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); + if (!pk) + return log_openssl_errors(LOG_DEBUG, "Failed to parse PEM private key"); + + *ret = TAKE_PTR(pk); + + return 0; +} + +static bool openssl_ui_supported(void) { +#ifndef OPENSSL_NO_UI_CONSOLE + return + sym_UI_OpenSSL && + sym_UI_create_method && + sym_UI_destroy_method && + sym_UI_get0_output_string && + sym_UI_get_default_method && + sym_UI_get_method && + sym_UI_get_string_type && + sym_UI_method_get_ex_data && + sym_UI_method_get_reader && + sym_UI_method_set_ex_data && + sym_UI_method_set_reader && + sym_UI_set_default_method && + sym_UI_set_result; +#else + return false; +#endif +} + +OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) { + if (!ui) + return NULL; + + assert(openssl_ui_supported()); + +#ifndef OPENSSL_NO_UI_CONSOLE + assert(sym_UI_get_default_method() == ui->method); + sym_UI_set_default_method(sym_UI_OpenSSL()); + sym_UI_destroy_method(ui->method); +#endif + return mfree(ui); +} + #ifndef OPENSSL_NO_UI_CONSOLE static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { int r; - switch(sym_UI_get_string_type(uis)) { + assert(openssl_ui_supported()); + + switch (sym_UI_get_string_type(uis)) { case UIT_PROMPT: { /* If no ask password request was configured use the default openssl UI. */ AskPasswordRequest *req = (AskPasswordRequest*) sym_UI_method_get_ex_data(sym_UI_get_method(ui), 0); @@ -2125,54 +2179,17 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { } #endif -static int openssl_load_private_key_from_file(const char *path, EVP_PKEY **ret) { - _cleanup_(erase_and_freep) char *rawkey = NULL; - _cleanup_(BIO_freep) BIO *kb = NULL; - _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL; - size_t rawkeysz; - int r; - - assert(path); - assert(ret); - - r = dlopen_libcrypto(LOG_DEBUG); - if (r < 0) - return r; - - r = read_full_file_full( - AT_FDCWD, path, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &rawkey, &rawkeysz); - if (r < 0) - return log_debug_errno(r, "Failed to read key file '%s': %m", path); - - kb = sym_BIO_new_mem_buf(rawkey, rawkeysz); - if (!kb) - return log_oom_debug(); - - pk = sym_PEM_read_bio_PrivateKey(kb, NULL, NULL, NULL); - if (!pk) - return log_openssl_errors(LOG_DEBUG, "Failed to parse PEM private key"); - - *ret = TAKE_PTR(pk); - - return 0; -} - static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSSLAskPasswordUI **ret) { -#ifndef OPENSSL_NO_UI_CONSOLE - int r; -#endif - assert(request); assert(ret); -#ifndef OPENSSL_NO_UI_CONSOLE - r = dlopen_libcrypto(LOG_DEBUG); - if (r < 0) - return r; + if (!openssl_ui_supported()) { + log_debug("OpenSSL UI API is not supported."); + *ret = NULL; + return 0; + } +#ifndef OPENSSL_NO_UI_CONSOLE _cleanup_(UI_destroy_methodp) UI_METHOD *method = sym_UI_create_method("systemd-ask-password"); if (!method) return log_openssl_errors(LOG_DEBUG, "Failed to initialize openssl user interface"); @@ -2197,10 +2214,84 @@ static int openssl_ask_password_ui_new(const AskPasswordRequest *request, OpenSS *ret = TAKE_PTR(ui); return 0; #else - return -EOPNOTSUPP; + assert_not_reached(); #endif } +int openssl_load_private_key( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + const AskPasswordRequest *request, + EVP_PKEY **ret_private_key, + OpenSSLAskPasswordUI **ret_user_interface) { + + int r; + + /* The caller must keep the OpenSSLAskPasswordUI object alive as long as the EVP_PKEY object so that + * the user can enter any needed hardware token pin to unlock the private key when needed. */ + + assert(private_key); + assert(request); + assert(ret_private_key); + assert(ret_user_interface); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; + + switch (private_key_source_type) { + case OPENSSL_KEY_SOURCE_FILE: + r = openssl_load_private_key_from_file(private_key, ret_private_key); + break; + case OPENSSL_KEY_SOURCE_ENGINE: + r = openssl_ask_password_ui_new(request, &ui); + if (r < 0) + return r; + + r = load_key_from_engine(private_key_source, private_key, ui ? ui->method : NULL, ret_private_key); + break; + case OPENSSL_KEY_SOURCE_PROVIDER: + r = openssl_ask_password_ui_new(request, &ui); + if (r < 0) + return r; + + r = load_key_from_provider(private_key_source, private_key, ui ? ui->method : NULL, ret_private_key); + break; + default: + assert_not_reached(); + } + if (r < 0) + return r; + + *ret_user_interface = TAKE_PTR(ui); + return 0; +} + +int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret) { + int r; + + assert(private_key); + assert(ret); + + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + + _cleanup_(OPENSSL_freep) void *buf = NULL; + int len = sym_i2d_PUBKEY(private_key, (unsigned char**) &buf); + if (len < 0) + return log_openssl_errors(LOG_DEBUG, "Failed to extract public key in DER format"); + + const unsigned char *t = buf; + if (!sym_d2i_PUBKEY(ret, &t, len)) + return log_openssl_errors(LOG_DEBUG, "Failed to parse public key in DER format"); + + return 0; +} + static int load_x509_certificate_from_file(const char *path, X509 **ret) { _cleanup_free_ char *rawcert = NULL; _cleanup_(X509_freep) X509 *cert = NULL; @@ -2211,10 +2302,6 @@ static int load_x509_certificate_from_file(const char *path, X509 **ret) { assert(path); assert(ret); - r = dlopen_libcrypto(LOG_DEBUG); - if (r < 0) - return r; - r = read_full_file_full( AT_FDCWD, path, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, @@ -2238,16 +2325,10 @@ static int load_x509_certificate_from_file(const char *path, X509 **ret) { } static int load_x509_certificate_from_provider(const char *provider, const char *certificate_uri, X509 **ret) { - int r; - assert(provider); assert(certificate_uri); assert(ret); - r = dlopen_libcrypto(LOG_DEBUG); - if (r < 0) - return r; - /* Load the provider so that this can work without any custom written configuration in /etc/. * Also load the 'default' as that seems to be the recommendation. */ if (!sym_OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) @@ -2280,20 +2361,8 @@ static int load_x509_certificate_from_provider(const char *provider, const char return 0; } -OpenSSLAskPasswordUI* openssl_ask_password_ui_free(OpenSSLAskPasswordUI *ui) { - if (!ui) - return NULL; - -#ifndef OPENSSL_NO_UI_CONSOLE - assert(sym_UI_get_default_method() == ui->method); - sym_UI_set_default_method(sym_UI_OpenSSL()); - sym_UI_destroy_method(ui->method); -#endif - return mfree(ui); -} - int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { - _cleanup_free_ uint8_t *der = NULL; + _cleanup_(OPENSSL_freep) void *der = NULL; int dersz, r; assert(cert); @@ -2302,7 +2371,7 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { if (r < 0) return r; - dersz = sym_i2d_X509(cert, &der); + dersz = sym_i2d_X509(cert, (unsigned char**) &der); if (dersz < 0) return log_openssl_errors(LOG_DEBUG, "Unable to convert PEM certificate to DER format"); @@ -2320,6 +2389,10 @@ int openssl_load_x509_certificate( assert(certificate); + r = dlopen_libcrypto(LOG_DEBUG); + if (r < 0) + return r; + switch (certificate_source_type) { case OPENSSL_CERTIFICATE_SOURCE_FILE: @@ -2340,96 +2413,6 @@ int openssl_load_x509_certificate( return 0; } - -int openssl_load_private_key( - KeySourceType private_key_source_type, - const char *private_key_source, - const char *private_key, - const AskPasswordRequest *request, - EVP_PKEY **ret_private_key, - OpenSSLAskPasswordUI **ret_user_interface) { - - int r; - - /* The caller must keep the OpenSSLAskPasswordUI object alive as long as the EVP_PKEY object so that - * the user can enter any needed hardware token pin to unlock the private key when needed. */ - - assert(private_key); - assert(request); - assert(ret_private_key); - assert(ret_user_interface); - - if (private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { - r = openssl_load_private_key_from_file(private_key, ret_private_key); - if (r < 0) - return r; - - *ret_user_interface = NULL; - } else { - _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; - r = openssl_ask_password_ui_new(request, &ui); - if (r < 0) - return log_debug_errno(r, "Failed to allocate ask-password user interface: %m"); - - UI_METHOD *ui_method = NULL; -#ifndef OPENSSL_NO_UI_CONSOLE - ui_method = ui->method; -#endif - - switch (private_key_source_type) { - - case OPENSSL_KEY_SOURCE_ENGINE: - r = load_key_from_engine(private_key_source, private_key, ui_method, ret_private_key); - break; - case OPENSSL_KEY_SOURCE_PROVIDER: - r = load_key_from_provider(private_key_source, private_key, ui_method, ret_private_key); - break; - default: - assert_not_reached(); - } - if (r < 0) - return log_debug_errno( - r, - "Failed to load key '%s' from OpenSSL private key source %s: %m", - private_key, - private_key_source); - - *ret_user_interface = TAKE_PTR(ui); - } - - return 0; -} - -int openssl_extract_public_key(EVP_PKEY *private_key, EVP_PKEY **ret) { - int r; - - assert(private_key); - assert(ret); - - r = dlopen_libcrypto(LOG_DEBUG); - if (r < 0) - return r; - - _cleanup_(memstream_done) MemStream m = {}; - FILE *tf = memstream_init(&m); - if (!tf) - return -ENOMEM; - - if (sym_i2d_PUBKEY_fp(tf, private_key) != 1) - return log_openssl_errors(LOG_DEBUG, "Failed to extract public key in DER format"); - - _cleanup_(erase_and_freep) char *buf = NULL; - size_t len; - r = memstream_finalize(&m, &buf, &len); - if (r < 0) - return r; - - const unsigned char *t = (const unsigned char*) buf; - if (!sym_d2i_PUBKEY(ret, &t, len)) - return log_openssl_errors(LOG_DEBUG, "Failed to parse public key in DER format"); - - return 0; -} #endif int parse_openssl_certificate_source_argument( diff --git a/src/shared/crypto-util.h b/src/shared/crypto-util.h index 53da4cabe6f56..9927dfb5c7bc3 100644 --- a/src/shared/crypto-util.h +++ b/src/shared/crypto-util.h @@ -33,6 +33,10 @@ int dlopen_libcrypto(int log_level); #define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE #if HAVE_OPENSSL +#ifndef SYSTEMD_CFLAGS_MARKER_LIBOPENSSL +# error "missing libopenssl_cflags in meson dependency." +#endif + #define LIBCRYPTO_NOTE(priority) \ SD_ELF_NOTE_DLOPEN("libcrypto", \ "Support for cryptographic operations", \ @@ -122,6 +126,10 @@ extern DLSYM_PROTOTYPE(BN_set_word); extern DLSYM_PROTOTYPE(BN_sub_word); extern DLSYM_PROTOTYPE(CRYPTO_free); extern DLSYM_PROTOTYPE(ECDSA_SIG_free); +extern DLSYM_PROTOTYPE(ECDSA_SIG_get0_r); +extern DLSYM_PROTOTYPE(ECDSA_SIG_get0_s); +extern DLSYM_PROTOTYPE(ECDSA_SIG_new); +extern DLSYM_PROTOTYPE(ECDSA_SIG_set0); extern DLSYM_PROTOTYPE(EC_GROUP_free); extern DLSYM_PROTOTYPE(EC_GROUP_get0_generator); extern DLSYM_PROTOTYPE(EC_GROUP_get0_order); @@ -175,18 +183,27 @@ extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_free); extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new); extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_from_name); extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_new_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_ec_paramgen_curve_nid); +extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_keygen_bits); extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_rsa_padding); extern DLSYM_PROTOTYPE(EVP_PKEY_CTX_set_signature_md); extern DLSYM_PROTOTYPE(EVP_PKEY_eq); extern DLSYM_PROTOTYPE(EVP_PKEY_free); extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata); extern DLSYM_PROTOTYPE(EVP_PKEY_fromdata_init); +extern DLSYM_PROTOTYPE(EVP_PKEY_generate); extern DLSYM_PROTOTYPE(EVP_PKEY_get_base_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_get_bn_param); extern DLSYM_PROTOTYPE(EVP_PKEY_get_id); +extern DLSYM_PROTOTYPE(EVP_PKEY_get_octet_string_param); +extern DLSYM_PROTOTYPE(EVP_PKEY_get_size); extern DLSYM_PROTOTYPE(EVP_PKEY_keygen); extern DLSYM_PROTOTYPE(EVP_PKEY_keygen_init); extern DLSYM_PROTOTYPE(EVP_PKEY_new); extern DLSYM_PROTOTYPE(EVP_PKEY_new_raw_public_key); +extern DLSYM_PROTOTYPE(EVP_PKEY_public_check); +extern DLSYM_PROTOTYPE(EVP_PKEY_sign); +extern DLSYM_PROTOTYPE(EVP_PKEY_sign_init); extern DLSYM_PROTOTYPE(EVP_PKEY_verify); extern DLSYM_PROTOTYPE(EVP_PKEY_verify_init); extern DLSYM_PROTOTYPE(EVP_aes_256_ctr); @@ -210,6 +227,7 @@ extern DLSYM_PROTOTYPE(OPENSSL_sk_value); extern DLSYM_PROTOTYPE(OSSL_EC_curve_nid2name); extern DLSYM_PROTOTYPE(OSSL_PARAM_BLD_free); extern DLSYM_PROTOTYPE(OSSL_PARAM_BLD_new); +extern DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_BN); extern DLSYM_PROTOTYPE(OSSL_PARAM_BLD_push_utf8_string); extern DLSYM_PROTOTYPE(OSSL_PARAM_BLD_to_param); extern DLSYM_PROTOTYPE(OSSL_PARAM_construct_BN); @@ -241,7 +259,6 @@ extern DLSYM_PROTOTYPE(PKCS7_new); extern DLSYM_PROTOTYPE(PKCS7_set_content); extern DLSYM_PROTOTYPE(PKCS7_sign); extern DLSYM_PROTOTYPE(PKCS7_verify); -extern DLSYM_PROTOTYPE(SHA1); extern DLSYM_PROTOTYPE(SHA512); extern DLSYM_PROTOTYPE(X509_ALGOR_free); extern DLSYM_PROTOTYPE(X509_ATTRIBUTE_free); @@ -256,39 +273,19 @@ extern DLSYM_PROTOTYPE(X509_get_pubkey); extern DLSYM_PROTOTYPE(X509_get_subject_name); extern DLSYM_PROTOTYPE(X509_gmtime_adj); extern DLSYM_PROTOTYPE(d2i_ASN1_OCTET_STRING); +extern DLSYM_PROTOTYPE(d2i_ECDSA_SIG); extern DLSYM_PROTOTYPE(d2i_ECPKParameters); extern DLSYM_PROTOTYPE(d2i_PKCS7); extern DLSYM_PROTOTYPE(d2i_PUBKEY); extern DLSYM_PROTOTYPE(d2i_X509); extern DLSYM_PROTOTYPE(i2d_ASN1_INTEGER); +extern DLSYM_PROTOTYPE(i2d_ECDSA_SIG); extern DLSYM_PROTOTYPE(i2d_PKCS7); extern DLSYM_PROTOTYPE(i2d_PKCS7_fp); extern DLSYM_PROTOTYPE(i2d_PUBKEY); extern DLSYM_PROTOTYPE(i2d_X509); extern DLSYM_PROTOTYPE(i2d_X509_NAME); -#if !defined(OPENSSL_NO_DEPRECATED_3_0) -DISABLE_WARNING_DEPRECATED_DECLARATIONS; -extern DLSYM_PROTOTYPE(ECDSA_SIG_new); -extern DLSYM_PROTOTYPE(ECDSA_SIG_set0); -extern DLSYM_PROTOTYPE(ECDSA_do_verify); -extern DLSYM_PROTOTYPE(EC_KEY_check_key); -extern DLSYM_PROTOTYPE(EC_KEY_free); -extern DLSYM_PROTOTYPE(EC_KEY_new); -extern DLSYM_PROTOTYPE(EC_KEY_set_group); -extern DLSYM_PROTOTYPE(EC_KEY_set_public_key); -extern DLSYM_PROTOTYPE(EVP_PKEY_assign); -extern DLSYM_PROTOTYPE(RSAPublicKey_dup); -extern DLSYM_PROTOTYPE(RSA_free); -extern DLSYM_PROTOTYPE(RSA_new); -extern DLSYM_PROTOTYPE(RSA_set0_key); -extern DLSYM_PROTOTYPE(RSA_size); -REENABLE_WARNING; - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(EC_KEY*, sym_EC_KEY_free, EC_KEY_freep, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(RSA*, sym_RSA_free, RSA_freep, NULL); -#endif - /* Mirrors of OpenSSL macros that go through our dlopen'd sym_* variants, so we don't end up linking against * libcrypto just for these. */ #define sym_BIO_get_md_ctx(b, mdcp) sym_BIO_ctrl((b), BIO_C_GET_MD_CTX, 0, (char*) (mdcp)) @@ -298,7 +295,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(RSA*, sym_RSA_free, RSA_freep, NULL); #define sym_BN_one(a) sym_BN_set_word(a, 1) #define sym_EVP_MD_CTX_get_size(ctx) sym_EVP_MD_get_size(sym_EVP_MD_CTX_get0_md(ctx)) #define sym_EVP_MD_CTX_get0_name(ctx) sym_EVP_MD_get0_name(sym_EVP_MD_CTX_get0_md(ctx)) -#define sym_EVP_PKEY_assign_RSA(pkey, rsa) sym_EVP_PKEY_assign((pkey), EVP_PKEY_RSA, (rsa)) #define sym_OPENSSL_free(addr) sym_CRYPTO_free((addr), OPENSSL_FILE, OPENSSL_LINE) #define sym_PKCS7_set_detached(p, v) sym_PKCS7_ctrl((p), PKCS7_OP_SET_DETACHED_SIGNATURE, (v), NULL) diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 1b3ab8b9604f0..6e9c95aa9e8f6 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -7,6 +7,10 @@ #include "shared-forward.h" #if HAVE_LIBCRYPTSETUP +#ifndef SYSTEMD_CFLAGS_MARKER_LIBCRYPTSETUP +# error "missing libcryptsetup_cflags in meson dependency." +#endif + #include /* IWYU pragma: export */ /* Available since libcryptsetup 2.7. Always redeclare so DLSYM_PROTOTYPE's typeof() resolves on older diff --git a/src/shared/curl-util.h b/src/shared/curl-util.h index 33bf3684c9eea..a010cefac1046 100644 --- a/src/shared/curl-util.h +++ b/src/shared/curl-util.h @@ -6,6 +6,10 @@ #include "shared-forward.h" #if HAVE_LIBCURL +#ifndef SYSTEMD_CFLAGS_MARKER_LIBCURL +# error "missing libcurl_cflags in meson dependency." +#endif + #include /* IWYU pragma: export */ #include "dlfcn-util.h" diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index 2210ab39f1423..5e40541ffcf9b 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -1,6 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_ELFUTILS +#ifndef SYSTEMD_CFLAGS_MARKER_LIBELF +# error "missing libelf_cflags in meson dependency." +#endif + #include #include #include diff --git a/src/shared/fdisk-util.h b/src/shared/fdisk-util.h index 367c6cd051e44..acdbd7c741cde 100644 --- a/src/shared/fdisk-util.h +++ b/src/shared/fdisk-util.h @@ -6,6 +6,9 @@ #include "shared-forward.h" #if HAVE_LIBFDISK +#ifndef SYSTEMD_CFLAGS_MARKER_LIBFDISK +# error "missing libfdisk_cflags in meson dependency." +#endif #include /* IWYU pragma: export */ diff --git a/src/shared/gnutls-util.h b/src/shared/gnutls-util.h index a110b437c3823..dd0f1d55acb3e 100644 --- a/src/shared/gnutls-util.h +++ b/src/shared/gnutls-util.h @@ -6,6 +6,10 @@ int dlopen_gnutls(int log_level); #if HAVE_GNUTLS +# ifndef SYSTEMD_CFLAGS_MARKER_LIBGNUTLS +# error "missing libgnutls_cflags in meson dependency." +# endif + # include /* IWYU pragma: export */ # include /* IWYU pragma: export */ diff --git a/src/shared/idn-util.h b/src/shared/idn-util.h index 9bed27140fde9..0f3db74c63289 100644 --- a/src/shared/idn-util.h +++ b/src/shared/idn-util.h @@ -6,6 +6,10 @@ #include "shared-forward.h" #if HAVE_LIBIDN2 +#ifndef SYSTEMD_CFLAGS_MARKER_LIBIDN2 +# error "missing libidn2_cflags in meson dependency." +#endif + #include #include "dlfcn-util.h" diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h index afaf3712007a8..71b4d66d42ce2 100644 --- a/src/shared/libarchive-util.h +++ b/src/shared/libarchive-util.h @@ -6,6 +6,10 @@ #include "shared-forward.h" #if HAVE_LIBARCHIVE +#ifndef SYSTEMD_CFLAGS_MARKER_LIBARCHIVE +# error "missing libarchive_cflags in meson dependency." +#endif + #include /* IWYU pragma: export */ #include /* IWYU pragma: export */ diff --git a/src/shared/libaudit-util.h b/src/shared/libaudit-util.h index 8bb05a2fbd3c1..d16e25df671ba 100644 --- a/src/shared/libaudit-util.h +++ b/src/shared/libaudit-util.h @@ -6,6 +6,10 @@ int dlopen_libaudit(int log_level); #if HAVE_AUDIT +# ifndef SYSTEMD_CFLAGS_MARKER_LIBAUDIT +# error "missing libaudit_cflags in meson dependency." +# endif + # include /* IWYU pragma: export */ # include "dlfcn-util.h" @@ -15,6 +19,26 @@ extern DLSYM_PROTOTYPE(audit_log_user_avc_message); extern DLSYM_PROTOTYPE(audit_log_user_comm_message); #endif +/* libaudit.h defines these constants. + * Define them here too so they can be used even when libaudit.h is unavailable. */ +#ifndef AUDIT_SERVICE_START +# define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ +#else +assert_cc(AUDIT_SERVICE_START == 1130); +#endif + +#ifndef AUDIT_SERVICE_STOP +# define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ +#else +assert_cc(AUDIT_SERVICE_STOP == 1131); +#endif + +#ifndef MAX_AUDIT_MESSAGE_LENGTH +# define MAX_AUDIT_MESSAGE_LENGTH 8970 +#else +assert_cc(MAX_AUDIT_MESSAGE_LENGTH == 8970); +#endif + bool use_audit(void); int close_audit_fd(int fd); diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index d9710566e6d2e..106d7688fe958 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_LIBCRYPT +# ifndef SYSTEMD_CFLAGS_MARKER_LIBCRYPT +# error "missing libcrypt_cflags in meson dependency." +# endif # include #endif diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index 12a0201332fa4..82705a7f23dd7 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -19,6 +19,10 @@ typedef enum Fido2EnrollFlags { int dlopen_libfido2(int log_level); #if HAVE_LIBFIDO2 +#ifndef SYSTEMD_CFLAGS_MARKER_LIBFIDO2 +# error "missing libfido2_cflags in meson dependency." +#endif + #include #include "dlfcn-util.h" diff --git a/src/shared/libmount-util.h b/src/shared/libmount-util.h index 44e58ec932040..3afdb9737885a 100644 --- a/src/shared/libmount-util.h +++ b/src/shared/libmount-util.h @@ -6,6 +6,9 @@ #include "shared-forward.h" #if HAVE_LIBMOUNT +#ifndef SYSTEMD_CFLAGS_MARKER_LIBMOUNT +# error "missing libmount_cflags in meson dependency." +#endif /* This needs to be after sys/mount.h */ #include /* IWYU pragma: export */ diff --git a/src/shared/meson.build b/src/shared/meson.build index f9367ac2265f0..466d86bb6f26c 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -256,6 +256,7 @@ shared_sources = files( 'varlink-io.systemd.Resolve.Monitor.c', 'varlink-io.systemd.Shutdown.c', 'varlink-io.systemd.StorageProvider.c', + 'varlink-io.systemd.SysInstall.c', 'varlink-io.systemd.SysUpdate.c', 'varlink-io.systemd.SysUpdate.Notify.c', 'varlink-io.systemd.Udev.c', @@ -405,7 +406,6 @@ libshared_deps = [libacl_cflags, libcrypt_cflags, libcryptsetup_cflags, libcurl_cflags, - libdw_cflags, libelf_cflags, libfdisk_cflags, libfido2_cflags, diff --git a/src/shared/metrics.c b/src/shared/metrics.c index cb00977b539c8..68b18895b4288 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -73,6 +73,20 @@ static int metric_family_build_json(const MetricFamily *mf, sd_json_variant **re SD_JSON_BUILD_PAIR_STRING("type", metric_family_type_to_string(mf->type))); } +int metric_family_describe(const MetricFamily *mf, sd_varlink *link) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(mf); + assert(link); + + r = metric_family_build_json(mf, &v); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + int metrics_method_describe( const MetricFamily mfs[], sd_varlink *link, @@ -95,15 +109,9 @@ int metrics_method_describe( return r; for (const MetricFamily *mf = mfs; mf->name; mf++) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - - r = metric_family_build_json(mf, &v); + r = metric_family_describe(mf, link); if (r < 0) return log_debug_errno(r, "Failed to describe metric family '%s': %m", mf->name); - - r = sd_varlink_reply(link, v); - if (r < 0) - return log_debug_errno(r, "Failed to send varlink reply: %m"); } return 0; diff --git a/src/shared/metrics.h b/src/shared/metrics.h index 8d6dcec999457..253e950fea7a6 100644 --- a/src/shared/metrics.h +++ b/src/shared/metrics.h @@ -34,6 +34,7 @@ int metrics_setup_varlink_server( DECLARE_STRING_TABLE_LOOKUP_TO_STRING(metric_family_type, MetricFamilyType); +int metric_family_describe(const MetricFamily *mf, sd_varlink *link); int metrics_method_describe(const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int metrics_method_list(const MetricFamily mfs[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/shared/microhttpd-util.h b/src/shared/microhttpd-util.h index 39b2b9267b2e3..c430f9e3d1a26 100644 --- a/src/shared/microhttpd-util.h +++ b/src/shared/microhttpd-util.h @@ -8,6 +8,10 @@ int dlopen_microhttpd(int log_level); #if HAVE_MICROHTTPD +#ifndef SYSTEMD_CFLAGS_MARKER_LIBMICROHTTPD +# error "missing libmicrohttpd_cflags in meson dependency." +#endif + #define MICROHTTPD_NOTE(priority) \ SD_ELF_NOTE_DLOPEN("microhttpd", \ "Support for embedded HTTP server via libmicrohttpd", \ diff --git a/src/shared/module-util.h b/src/shared/module-util.h index 730e14c31b32a..59d2f3b1f1e07 100644 --- a/src/shared/module-util.h +++ b/src/shared/module-util.h @@ -6,6 +6,9 @@ #include "shared-forward.h" #if HAVE_KMOD +#ifndef SYSTEMD_CFLAGS_MARKER_LIBKMOD +# error "missing libkmod_cflags in meson dependency." +#endif #include /* IWYU pragma: export */ diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 408c371e8d743..41591a4d36cfc 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -6,6 +6,10 @@ #include "shared-forward.h" #if HAVE_PAM +#ifndef SYSTEMD_CFLAGS_MARKER_LIBPAM +# error "missing libpam_cflags in meson dependency." +#endif + #include #include #include /* IWYU pragma: export */ diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c index f32245d344cbe..8d762f7e5cc6d 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -6,6 +6,9 @@ #include "log.h" /* IWYU pragma: keep */ #if HAVE_PASSWDQC +#ifndef SYSTEMD_CFLAGS_MARKER_LIBPWQUALITY +# error "missing libpwquality_cflags in meson dependency." +#endif #include diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c index 34d9b1aa16e26..9952c4d195b27 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -6,6 +6,9 @@ #include "log.h" #if HAVE_PWQUALITY +#ifndef SYSTEMD_CFLAGS_MARKER_LIBPWQUALITY +# error "missing libpwquality_cflags in meson dependency." +#endif #include #include diff --git a/src/shared/pcre2-util.h b/src/shared/pcre2-util.h index ba8827fb0749b..6b24dc49824e0 100644 --- a/src/shared/pcre2-util.h +++ b/src/shared/pcre2-util.h @@ -4,6 +4,9 @@ #include "shared-forward.h" #if HAVE_PCRE2 +#ifndef SYSTEMD_CFLAGS_MARKER_LIBPCRE2 +# error "missing libpcre2_cflags in meson dependency." +#endif #include "dlfcn-util.h" diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 60235fa3e0882..6002c2e0af73d 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -1,9 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#if HAVE_OPENSSL -# include -#endif - #include "sd-dlopen.h" #include "alloc-util.h" diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index f4599a50f16c6..141fd29c338be 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -2,6 +2,9 @@ #pragma once #if HAVE_P11KIT +# ifndef SYSTEMD_CFLAGS_MARKER_LIBP11KIT +# error "missing libp11kit_cflags in meson dependency." +# endif # include /* IWYU pragma: export */ # include /* IWYU pragma: export */ #endif diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index 44d674ba6a3ae..a5bf75f88918c 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -3,6 +3,9 @@ #include "qrcode-util.h" #if HAVE_QRENCODE +#ifndef SYSTEMD_CFLAGS_MARKER_LIBQRENCODE +# error "missing libqrencode_cflags in meson dependency." +#endif #include #endif #include diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c index bd04fee342154..758b7a2600b06 100644 --- a/src/shared/reboot-util.c +++ b/src/shared/reboot-util.c @@ -5,6 +5,10 @@ #include #if HAVE_XENCTRL +#ifndef SYSTEMD_CFLAGS_MARKER_LIBXENCTRL +# error "missing libxenctrl_cflags in meson dependency." +#endif + #include #include diff --git a/src/shared/seccomp-util.h b/src/shared/seccomp-util.h index 89dcaf5ca74b0..2bcb2d935bad0 100644 --- a/src/shared/seccomp-util.h +++ b/src/shared/seccomp-util.h @@ -7,6 +7,10 @@ #include "shared-forward.h" #if HAVE_SECCOMP +#ifndef SYSTEMD_CFLAGS_MARKER_LIBSECCOMP +# error "missing libseccomp_cflags in meson dependency." +#endif + #include /* IWYU pragma: export */ #include "dlfcn-util.h" diff --git a/src/shared/selinux-util.h b/src/shared/selinux-util.h index 98cfb6bbe40b4..3cc48dd3acd92 100644 --- a/src/shared/selinux-util.h +++ b/src/shared/selinux-util.h @@ -8,6 +8,10 @@ #include "shared-forward.h" #if HAVE_SELINUX +#ifndef SYSTEMD_CFLAGS_MARKER_LIBSELINUX +# error "missing libselinux_cflags in meson dependency." +#endif + #include #include #include diff --git a/src/shared/ssl-util.h b/src/shared/ssl-util.h index 857d4b5e17356..3a9a4084da34d 100644 --- a/src/shared/ssl-util.h +++ b/src/shared/ssl-util.h @@ -8,6 +8,10 @@ int dlopen_libssl(int log_level); #if HAVE_OPENSSL +#ifndef SYSTEMD_CFLAGS_MARKER_LIBOPENSSL +# error "missing libopenssl_cflags in meson dependency." +#endif + #define LIBSSL_NOTE(priority) \ SD_ELF_NOTE_DLOPEN("libssl", \ "Support for TLS", \ diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 3a9fbaa7e47e3..27606ab73d04c 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -7183,9 +7183,9 @@ static int tpm2_define_nvpcr_nv_index( const Tpm2Handle *session, TPM2_HANDLE nv_index, TPMI_ALG_HASH algorithm, + bool orderly, Tpm2Handle **ret_nv_handle) { - _cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL; TSS2_RC rc; int r; @@ -7203,11 +7203,13 @@ static int tpm2_define_nvpcr_nv_index( if ((size_t) digest_size > sizeof_field(TPM2B_MAX_NV_BUFFER, buffer)) return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Digest too large for extension."); - r = tpm2_handle_new(c, &new_handle); - if (r < 0) - return r; - - new_handle->flush = false; /* This is a persistent NV index, don't flush hence */ + /* If we already ran into NV index space exhaustion for this orderly mode during this boot, don't + * bother trying again — the situation is unlikely to improve until reboot. We track this via a flag + * file in /run/, with a separate file for orderly and non-orderly NvPCRs, since the two draw on + * different TPM resources (RAM-backed vs. NVRAM-backed). */ + const char *exhausted_flag = orderly ? + "/run/systemd/tpm2-nv-space-exhausted-orderly" : + "/run/systemd/tpm2-nv-space-exhausted-non-orderly"; TPM2B_NV_PUBLIC public_info = { .size = sizeof_field(TPM2B_NV_PUBLIC, nvPublic), @@ -7215,7 +7217,7 @@ static int tpm2_define_nvpcr_nv_index( .nvIndex = nv_index, .nameAlg = algorithm, .attributes = TPMA_NV_CLEAR_STCLEAR | - TPMA_NV_ORDERLY | + (orderly ? TPMA_NV_ORDERLY : 0) | TPMA_NV_OWNERWRITE | TPMA_NV_AUTHWRITE | TPMA_NV_OWNERREAD | @@ -7225,62 +7227,93 @@ static int tpm2_define_nvpcr_nv_index( }, }; - rc = sym_Esys_NV_DefineSpace( - c->esys_context, - /* authHandle= */ ESYS_TR_RH_OWNER, - /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, - /* shandle2= */ ESYS_TR_NONE, - /* shandle3= */ ESYS_TR_NONE, - /* auth= */ NULL, - &public_info, - &new_handle->esys_handle); - if (rc == TPM2_RC_NV_SPACE) - return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), - "NV index space on TPM exhausted, cannot allocate NvPCR."); - if (rc == TPM2_RC_NV_DEFINED) { - log_debug("NV index 0x%" PRIu32 " already registered.", nv_index); - - new_handle = tpm2_handle_free(new_handle); - - _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public_real = NULL; - r = tpm2_nv_index_to_handle( - c, - nv_index, - session, - &nv_public_real, - /* ret_name= */ NULL, - &new_handle); - if (r <= 0) - return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to acquire handle to existing NV index 0x%" PRIu32 ".", nv_index); - - log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", nv_index); - - if (nv_public_real->size < endoffsetof_field(TPMS_NV_PUBLIC, attributes) + sizeof_field(TPMS_NV_PUBLIC, dataSize) || - nv_public_real->nvPublic.nvIndex != public_info.nvPublic.nvIndex || - nv_public_real->nvPublic.nameAlg != public_info.nvPublic.nameAlg || - ((nv_public_real->nvPublic.attributes ^ public_info.nvPublic.attributes) & ~TPMA_NV_WRITTEN) != 0 || - nv_public_real->nvPublic.dataSize != public_info.nvPublic.dataSize) - return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), - "Public data of nvindex 0x%x does not match our expectations.", nv_index); + bool exhausted; + if (access(exhausted_flag, F_OK) < 0) { + if (errno != ENOENT) + log_debug_errno(errno, "Failed to check whether %s exists, assuming it does not: %m", exhausted_flag); - log_debug("Public info for nvindex 0x%x checks out, using.", nv_index); + _cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL; + r = tpm2_handle_new(c, &new_handle); + if (r < 0) + return r; - if (ret_nv_handle) - *ret_nv_handle = TAKE_PTR(new_handle); + new_handle->flush = false; /* This is a persistent NV index, don't flush hence */ - return 0; + rc = sym_Esys_NV_DefineSpace( + c->esys_context, + /* authHandle= */ ESYS_TR_RH_OWNER, + /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + /* auth= */ NULL, + &public_info, + &new_handle->esys_handle); + if (rc == TPM2_RC_NV_SPACE) { + /* Remember that we ran out of NV index space for this orderly mode, so that we don't keep + * retrying the (doomed) allocation until reboot. */ + r = touch(exhausted_flag); + if (r < 0) + log_debug_errno(r, "Failed to create %s flag file, ignoring: %m", exhausted_flag); + + return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), + "NV index space on TPM exhausted, cannot allocate NvPCR."); + } + if (rc == TSS2_RC_SUCCESS) { + log_debug("NV Index 0x%" PRIx32 " successfully allocated.", nv_index); + + if (ret_nv_handle) + *ret_nv_handle = TAKE_PTR(new_handle); + + return 1; + } + if (rc != TPM2_RC_NV_DEFINED) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to allocate NV index: %s", sym_Tss2_RC_Decode(rc)); + + log_debug("NV index 0x%" PRIx32 " already registered.", nv_index); + exhausted = false; + } else { + log_debug("TPM NV index space previously found exhausted (%s exists), refusing to allocate %s NvPCR, but checking if it already exists.", + exhausted_flag, orderly ? "orderly" : "non-orderly"); + exhausted = true; } - if (rc != TSS2_RC_SUCCESS) - return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to allocate NV index: %s", sym_Tss2_RC_Decode(rc)); - log_debug("NV Index 0x%" PRIx32 " successfully allocated.", nv_index); + /* We either got told that this NV index already exists or we didn't even try to allocate it, because + * it failed before. Let's get information about it, in the hope it exists. */ + + _cleanup_(Esys_Freep) TPM2B_NV_PUBLIC *nv_public_real = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *new_handle = NULL; + r = tpm2_nv_index_to_handle( + c, + nv_index, + session, + &nv_public_real, + /* ret_name= */ NULL, + &new_handle); + if (r <= 0) { + if (exhausted) + return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS), "Unable to acquire NvPCR and space exhaustion was indicated before."); + + return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to acquire handle to NV index 0x%" PRIx32 ".", nv_index); + } + + log_debug("Successfully acquired handle to existing NV index 0x%" PRIx32 ".", nv_index); + + if (nv_public_real->size < endoffsetof_field(TPMS_NV_PUBLIC, attributes) + sizeof_field(TPMS_NV_PUBLIC, dataSize) || + nv_public_real->nvPublic.nvIndex != public_info.nvPublic.nvIndex || + nv_public_real->nvPublic.nameAlg != public_info.nvPublic.nameAlg || + ((nv_public_real->nvPublic.attributes ^ public_info.nvPublic.attributes) & ~(TPMA_NV_WRITTEN|TPMA_NV_ORDERLY)) != 0 || + nv_public_real->nvPublic.dataSize != public_info.nvPublic.dataSize) + return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), + "Public data of nvindex 0x%" PRIx32 " does not match our expectations.", nv_index); + + log_debug("Public info for nvindex 0x%" PRIx32 " checks out, using.", nv_index); if (ret_nv_handle) *ret_nv_handle = TAKE_PTR(new_handle); - return 1; + return 0; } static int tpm2_extend_nvpcr_nv_index( @@ -7976,6 +8009,7 @@ typedef struct NvPCRData { uint16_t algorithm; uint32_t nv_index; uint64_t priority; + bool orderly; } NvPCRData; static void nvpcr_data_done(NvPCRData *d) { @@ -8016,12 +8050,14 @@ static int nvpcr_data_load(const char *name, NvPCRData *ret) { { "algorithm", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_tpm2_algorithm, offsetof(NvPCRData, algorithm), 0 }, { "nvIndex", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(NvPCRData, nv_index), SD_JSON_MANDATORY }, { "priority", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(NvPCRData, priority), 0 }, + { "orderly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(NvPCRData, orderly), 0 }, {}, }; _cleanup_(nvpcr_data_done) NvPCRData p = { .algorithm = TPM2_ALG_SHA256, .priority = TPM2_NVPCR_PRIORITY_DEFAULT, + .orderly = true, }; r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) @@ -8652,6 +8688,7 @@ int tpm2_nvpcr_initialize( session, p.nv_index, p.algorithm, + p.orderly, &nv_handle); if (r < 0) return r; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index eb910b1e84c3b..88098acac8ca7 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -50,6 +50,10 @@ static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { int dlopen_tpm2(int log_level); #if HAVE_TPM2 +#ifndef SYSTEMD_CFLAGS_MARKER_TPM2 +# error "missing tpm2_cflags in meson dependency." +#endif + #define TPM2_ESYS_NOTE(priority) \ SD_ELF_NOTE_DLOPEN("tpm", "Support for TPM", priority, "libtss2-esys.so.0") #define TPM2_RC_NOTE(priority) \ diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index cb1b0665d6092..76bcf7b3f39cc 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -48,10 +48,10 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_INPUT(leader, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The leader PID as ProcessId structure. If both the leader and leaderProcessId parameters are specified they must reference the same process. Typically one would only specify one or the other however. It's generally recommended to specify leaderProcessId as it references a process in a robust way without risk of identifier recycling."), SD_VARLINK_DEFINE_INPUT_BY_TYPE(leaderProcessId, ProcessId, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("The supervisor PID as simple positive integer."), - SD_VARLINK_DEFINE_INPUT(supervisor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("The supervisor PID as ProcessId structure. If both the supervisor and supervisorProcessId parameters are specified they must reference the same process. Typically only one or the other would be specified. It's generally recommended to specify supervisorProcessId as it references a process in a robust way without risk of identifier recycling."), - SD_VARLINK_DEFINE_INPUT_BY_TYPE(supervisorProcessId, ProcessId, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The supervisor PID as simple positive integer. Deprecated and ignored: the supervisor is now always derived from the caller's socket (kept for backward compatibility)."), + SD_VARLINK_DEFINE_INPUT(supervisor, SD_VARLINK_INT, SD_VARLINK_NULLABLE), /* for backward compat, ignored */ + SD_VARLINK_FIELD_COMMENT("The supervisor PID as ProcessId structure. Deprecated and ignored: the supervisor is now always derived from the caller's socket (kept for backward compatibility)."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(supervisorProcessId, ProcessId, SD_VARLINK_NULLABLE), /* for backward compat, ignored */ SD_VARLINK_DEFINE_INPUT(rootDirectory, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(ifIndices, SD_VARLINK_INT, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_INPUT(vSockCid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), diff --git a/src/shared/varlink-io.systemd.Repart.c b/src/shared/varlink-io.systemd.Repart.c index fcfdcd3d650a8..968d643f017c0 100644 --- a/src/shared/varlink-io.systemd.Repart.c +++ b/src/shared/varlink-io.systemd.Repart.c @@ -29,7 +29,7 @@ static SD_VARLINK_DEFINE_ENUM_TYPE( SD_VARLINK_FIELD_COMMENT("Always create a new partition table, potentially overwriting an existing table"), SD_VARLINK_DEFINE_ENUM_VALUE(force)); -static SD_VARLINK_DEFINE_ENUM_TYPE( +SD_VARLINK_DEFINE_ENUM_TYPE( BlockDeviceAction, SD_VARLINK_FIELD_COMMENT("The device is currently present and a candidate. Emitted both during the initial enumeration and for live uevents (any action other than 'remove' is propagated as 'add', including the synthesized transitions when a device becomes empty or read-only and a relevant ignore* input is in effect)."), SD_VARLINK_DEFINE_ENUM_VALUE(add), @@ -93,9 +93,9 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_DEFINE_OUTPUT(subsystem, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_ERROR(NoCandidateDevices); -static SD_VARLINK_DEFINE_ERROR(ConflictingDiskLabelPresent); -static SD_VARLINK_DEFINE_ERROR( +SD_VARLINK_DEFINE_ERROR(NoCandidateDevices); +SD_VARLINK_DEFINE_ERROR(ConflictingDiskLabelPresent); +SD_VARLINK_DEFINE_ERROR( InsufficientFreeSpace, SD_VARLINK_FIELD_COMMENT("Minimal size of the disk required for the installation."), SD_VARLINK_DEFINE_FIELD(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), @@ -103,7 +103,7 @@ static SD_VARLINK_DEFINE_ERROR( SD_VARLINK_DEFINE_FIELD(needFreeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("Size of the selected block device."), SD_VARLINK_DEFINE_FIELD(currentSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); -static SD_VARLINK_DEFINE_ERROR( +SD_VARLINK_DEFINE_ERROR( DiskTooSmall, SD_VARLINK_FIELD_COMMENT("Minimal size of the disk required for the installation."), SD_VARLINK_DEFINE_FIELD(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), diff --git a/src/shared/varlink-io.systemd.Repart.h b/src/shared/varlink-io.systemd.Repart.h index 7c7cb7dd79f86..536ea88ccdd98 100644 --- a/src/shared/varlink-io.systemd.Repart.h +++ b/src/shared/varlink-io.systemd.Repart.h @@ -4,3 +4,9 @@ #include "sd-varlink-idl.h" extern const sd_varlink_interface vl_interface_io_systemd_Repart; +extern const sd_varlink_symbol vl_type_BlockDeviceAction; + +extern const sd_varlink_symbol vl_error_NoCandidateDevices; +extern const sd_varlink_symbol vl_error_ConflictingDiskLabelPresent; +extern const sd_varlink_symbol vl_error_InsufficientFreeSpace; +extern const sd_varlink_symbol vl_error_DiskTooSmall; diff --git a/src/shared/varlink-io.systemd.SysInstall.c b/src/shared/varlink-io.systemd.SysInstall.c new file mode 100644 index 0000000000000..c93a0276fae06 --- /dev/null +++ b/src/shared/varlink-io.systemd.SysInstall.c @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink-idl.h" + +#include "varlink-io.systemd.SysInstall.h" +#include "varlink-io.systemd.Repart.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + ProgressPhase, + + SD_VARLINK_DEFINE_ENUM_VALUE(encrypt_credentials), + SD_VARLINK_DEFINE_ENUM_VALUE(install_partitions), + SD_VARLINK_DEFINE_ENUM_VALUE(mount_partitions), + SD_VARLINK_DEFINE_ENUM_VALUE(install_kernel), + SD_VARLINK_DEFINE_ENUM_VALUE(install_bootloader), + SD_VARLINK_DEFINE_ENUM_VALUE(unmount_partitions)); + +static SD_VARLINK_DEFINE_ENUM_TYPE( + DeviceFit, + + SD_VARLINK_DEFINE_ENUM_VALUE(enough_free_space), + SD_VARLINK_DEFINE_ENUM_VALUE(insufficent_free_space), + SD_VARLINK_DEFINE_ENUM_VALUE(disk_too_small), + SD_VARLINK_DEFINE_ENUM_VALUE(conflicting_disk_label_present)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + Credential, + SD_VARLINK_FIELD_COMMENT("The id of the credential."), + SD_VARLINK_DEFINE_FIELD(id, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("The value of the credential in base64 encoding."), + SD_VARLINK_DEFINE_FIELD(value, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + Run, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("Full path to the block device node to operate on."), + SD_VARLINK_DEFINE_INPUT(node, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Path to directory containing definition files."), + SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true, fully erase the target block device."), + SD_VARLINK_DEFINE_INPUT(erase, SD_VARLINK_BOOL, 0), + SD_VARLINK_FIELD_COMMENT("If true, EFI variables are modified to register the installed boot loader in the firmware's boot options database."), + SD_VARLINK_DEFINE_INPUT(variables, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The path to a kernel image, if missing the current kernel is dedected and used."), + SD_VARLINK_DEFINE_INPUT(kernelImagePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + + SD_VARLINK_FIELD_COMMENT("If true, the current locale is copied to target system"), + SD_VARLINK_DEFINE_INPUT(copyLocale, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true, the current keymap is copied to target system"), + SD_VARLINK_DEFINE_INPUT(copyKeymap, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true, the current timezone is copied to target system"), + SD_VARLINK_DEFINE_INPUT(copyTimezone, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + + SD_VARLINK_FIELD_COMMENT("A list of credentials to be installed to target system."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(credentials, Credential, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + + SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a phase identifier is sent in progress updates."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(phase, ProgressPhase, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, an object identifier string is sent in progress updates."), + SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a progress percentage (specific to the work done for the specified phase+object is sent in progress updates)."), + SD_VARLINK_DEFINE_OUTPUT(progress, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + ListCandidateDevices, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Path to directory containing definition files, used to evaluate the fit of the target OS for each block device."), + SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("If true, keep the call open after the initial enumeration and stream live add/remove notifications as block-subsystem uevents arrive. The end of the initial enumeration is marked by exactly one notification with action='ready' and no other fields. Defaults to false."), + SD_VARLINK_DEFINE_INPUT(subscribe, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Discriminator field. Only set in subscribe mode. 'add' carries the full device record, 'remove' carries only node, 'ready' carries no other fields and is sent once after the initial enumeration."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(action, BlockDeviceAction, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The device node path of the block device."), + SD_VARLINK_DEFINE_OUTPUT(node, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("List of symlinks pointing to the device node, if any."), + SD_VARLINK_DEFINE_OUTPUT(symlinks, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The Linux kernel disk sequence number identifying the medium."), + SD_VARLINK_DEFINE_OUTPUT(diskseq, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The size of the block device in bytes."), + SD_VARLINK_DEFINE_OUTPUT(sizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The device vendor string if known."), + SD_VARLINK_DEFINE_OUTPUT(vendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The device model string if known."), + SD_VARLINK_DEFINE_OUTPUT(model, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The subsystem the block device belongs to if known."), + SD_VARLINK_DEFINE_OUTPUT(subsystem, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The fit indicating whether the OS would have enough available space on the device."), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(fit, DeviceFit, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Minimal size of the disk required for the installation."), + SD_VARLINK_DEFINE_OUTPUT(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Additional free space needed for the installation."), + SD_VARLINK_DEFINE_OUTPUT(needFreeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Current allocated size of this block device."), + SD_VARLINK_DEFINE_OUTPUT(currentSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_SysInstall, + "io.systemd.SysInstall", + SD_VARLINK_INTERFACE_COMMENT("API for installing the OS to another block device."), + + SD_VARLINK_SYMBOL_COMMENT("Progress phase identifiers. Note that we might add more phases here, and thus identifiers. Frontends can choose to display the phase to the user in some human readable form, or not do that, but if they do it and they receive a notification for a so far unknown phase, they should just ignore it."), + &vl_type_ProgressPhase, + + SD_VARLINK_SYMBOL_COMMENT("Description about the fit of an OS on a block device."), + &vl_type_DeviceFit, + + SD_VARLINK_SYMBOL_COMMENT("Discriminator on streamed ListCandidateDevices replies in subscribe mode."), + &vl_type_BlockDeviceAction, + + SD_VARLINK_SYMBOL_COMMENT("A credential to install to target OS."), + &vl_type_Credential, + + SD_VARLINK_SYMBOL_COMMENT("Invoke the actual installation of the OS. If invoked with 'more' enabled will report progress, otherwise will just report completion."), + &vl_method_Run, + SD_VARLINK_SYMBOL_COMMENT("An incompatible disk label present, and not told to erase it."), + &vl_error_ConflictingDiskLabelPresent, + SD_VARLINK_SYMBOL_COMMENT("The target disk has insufficient free space to fit all requested partitions. (But the disk would fit, if emptied.)"), + &vl_error_InsufficientFreeSpace, + SD_VARLINK_SYMBOL_COMMENT("The target disk is too small to fit the installation. (Regardless if emptied or not.)"), + &vl_error_DiskTooSmall, + + SD_VARLINK_SYMBOL_COMMENT("Return a list of candidate block devices, i.e. that support partition scanning and other requirements for successful operation."), + &vl_method_ListCandidateDevices, + SD_VARLINK_SYMBOL_COMMENT("Not a single candidate block device could be found."), + &vl_error_NoCandidateDevices); diff --git a/src/shared/varlink-io.systemd.SysInstall.h b/src/shared/varlink-io.systemd.SysInstall.h new file mode 100644 index 0000000000000..d412e07b03c47 --- /dev/null +++ b/src/shared/varlink-io.systemd.SysInstall.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_SysInstall; diff --git a/src/sysext/meson.build b/src/sysext/meson.build index 75d06163c0c5e..2df28f4398581 100644 --- a/src/sysext/meson.build +++ b/src/sysext/meson.build @@ -10,6 +10,7 @@ executables += [ libblkid_cflags, libcryptsetup_cflags, libmount_cflags, + libselinux_cflags, ], }, ] diff --git a/src/sysinstall/io.systemd.sysinstall.policy b/src/sysinstall/io.systemd.sysinstall.policy new file mode 100644 index 0000000000000..d6bc796e52117 --- /dev/null +++ b/src/sysinstall/io.systemd.sysinstall.policy @@ -0,0 +1,40 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Install OS to disk + Authentication is required to install an OS to disk. + + auth_admin + auth_admin + auth_admin_keep + + + + + List candidate devices + Authentication is required to list device candidates. + + auth_admin + auth_admin + auth_admin_keep + + + diff --git a/src/sysinstall/meson.build b/src/sysinstall/meson.build index 1d8be6036564b..79618d69338a5 100644 --- a/src/sysinstall/meson.build +++ b/src/sysinstall/meson.build @@ -8,3 +8,8 @@ executables += [ 'sources' : files('sysinstall.c'), }, ] + +if conf.get('ENABLE_SYSINSTALL') == 1 + install_data('io.systemd.sysinstall.policy', + install_dir : polkitpolicydir) +endif diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c index 5d684cb0abc65..eca9681b378cf 100644 --- a/src/sysinstall/sysinstall.c +++ b/src/sysinstall/sysinstall.c @@ -11,6 +11,7 @@ #include "blockdev-list.h" #include "build.h" #include "build-path.h" +#include "bus-polkit.h" #include "chase.h" #include "conf-files.h" #include "constants.h" @@ -24,6 +25,7 @@ #include "format-util.h" #include "fs-util.h" #include "glyph-util.h" +#include "hashmap.h" #include "help-util.h" #include "image-policy.h" #include "json-util.h" @@ -39,8 +41,10 @@ #include "parse-util.h" #include "path-util.h" #include "prompt-util.h" +#include "string-table.h" #include "strv.h" #include "terminal-util.h" +#include "varlink-io.systemd.SysInstall.h" #include "varlink-util.h" static char *arg_node = NULL; @@ -58,12 +62,98 @@ static bool arg_copy_keymap = true; static bool arg_copy_timezone = true; static bool arg_chrome = true; static bool arg_mute_console = false; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_kernel_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); +typedef enum ProgressPhase { + PROGRESS_ENCRYPT_CREDENTIALS, + PROGRESS_INSTALL_PARTITIONS, + PROGRESS_MOUNT_PARTITIONS, + PROGRESS_INSTALL_KERNEL, + PROGRESS_INSTALL_BOOTLOADER, + PROGRESS_UNMOUNT_PARTITIONS, + _PROGRESS_PHASE_MAX, + _PROGRESS_PHASE_INVALID = -EINVAL, +} ProgressPhase; + +static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { + [PROGRESS_ENCRYPT_CREDENTIALS] = "encrypt-credentials", + [PROGRESS_INSTALL_PARTITIONS] = "install-partitions", + [PROGRESS_MOUNT_PARTITIONS] = "mount-partitions", + [PROGRESS_INSTALL_KERNEL] = "install-kernel", + [PROGRESS_INSTALL_BOOTLOADER] = "install-bootloader", + [PROGRESS_UNMOUNT_PARTITIONS] = "unmount-partitions", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase); + +static const char *progress_phase_log_table[_PROGRESS_PHASE_MAX] = { + [PROGRESS_ENCRYPT_CREDENTIALS] = "Encrypting credentials...", + [PROGRESS_INSTALL_PARTITIONS] = "Installing partitions...", + [PROGRESS_MOUNT_PARTITIONS] = "Mounting partitions...", + [PROGRESS_INSTALL_KERNEL] = "Installing kernel...", + [PROGRESS_INSTALL_BOOTLOADER] = "Installing boot loader...", + [PROGRESS_UNMOUNT_PARTITIONS] = "Unmounting partitions...", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase_log, ProgressPhase); + +typedef enum DeviceFit { + DEVICE_FIT_ENOUGH_FREE_SPACE, + DEVICE_FIT_INSUFFICIENT_FREE_SPACE, + DEVICE_FIT_DISK_TOO_SMALL, + DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT, + _DEVICE_FIT_MAX, + _DEVICE_FIT_INVALID = -EINVAL, +} DeviceFit; + +static const char *device_fit_table[_DEVICE_FIT_MAX] = { + [DEVICE_FIT_ENOUGH_FREE_SPACE] = "enough-free-space", + [DEVICE_FIT_INSUFFICIENT_FREE_SPACE] = "insufficient-free-space", + [DEVICE_FIT_DISK_TOO_SMALL] = "disk-too-small", + [DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT] = "conflicting-disk-label-present", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(device_fit, DeviceFit); + +typedef struct SysInstallContext { + bool copy_locale; + bool copy_keymap; + bool copy_timezone; + MachineCredentialContext credentials; + char **definitions; + bool erase; + char *node; + char *kernel_filename; + int kernel_fd; + bool touch_variables; + + sd_varlink *repart_link; + + sd_varlink *link; /* If 'more' is used on the Varlink call, we'll send progress info over this link */ +} SysInstallContext; + +static void sysinstall_context_done(SysInstallContext *c) { + assert(c); + + strv_free(c->definitions); + + free(c->node); + + free(c->kernel_filename); + safe_close(c->kernel_fd); + + machine_credential_context_done(&c->credentials); + + sd_varlink_flush_close_unref(c->repart_link); + + sd_varlink_unref(c->link); +} + static int help(void) { int r; @@ -210,6 +300,12 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } + r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) + arg_varlink = true; + return 1; } @@ -403,6 +499,195 @@ static int prompt_block_device(sd_varlink **repart_link, char **ret_node) { return 0; } +static int sysinstall_context_notify( + SysInstallContext *context, + ProgressPhase phase, + const char *object, + unsigned percent) { + + int r; + + assert(context); + assert(phase >= 0); + assert(phase < _PROGRESS_PHASE_MAX); + + log_notice("%s%s%s", + emoji_enabled() ? phase == PROGRESS_ENCRYPT_CREDENTIALS ? glyph(GLYPH_LOCK_AND_KEY) : glyph(GLYPH_COMPUTER_DISK) : "", + emoji_enabled() ? " " : "", + progress_phase_log_to_string(phase)); + + if (context->link) { + r = sd_varlink_notifybo( + context->link, + JSON_BUILD_PAIR_ENUM("phase", progress_phase_to_string(phase)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("progress", percent, UINT_MAX)); + if (r < 0) + log_debug_errno(r, "Failed to send varlink notify progress notification, ignoring: %m"); + + r = sd_varlink_flush(context->link); + if (r < 0) + log_debug_errno(r, "Failed to flush varlink notify progress notification, ignoring: %m"); + } + + return 0; +} + +typedef struct RepartResult { + int ret; + SysInstallContext *context; +} RepartResult; + +static int handle_repart_reply( + sd_varlink *link, + sd_json_variant *reply, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + RepartResult *result = userdata; + int r; + + assert(result); + + struct { + uint64_t min_size; + uint64_t current_size; + uint64_t need_free; + + const char *phase; + const char *object; + unsigned progress; + } p = { + .min_size = UINT64_MAX, + .current_size = UINT64_MAX, + .need_free = UINT64_MAX, + .progress = UINT_MAX, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, min_size), 0 }, + { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, current_size), 0 }, + { "needFreeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, need_free), 0 }, + { "phase", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, voffsetof(p, phase), 0 }, + { "object", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, voffsetof(p, object), 0 }, + { "progress", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, progress), 0 }, + {} + }; + + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return result->ret = r; + + if (error_id) { + const char *sysinstall_error_id = NULL; + + if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) { + sysinstall_error_id = "io.systemd.SysInstall.InsufficientFreeSpace"; + result->ret = log_error_errno(SYNTHETIC_ERRNO(ENOSPC), "Not enough free space on disk, cannot install."); + } else if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) { + sysinstall_error_id = "io.systemd.SysInstall.DiskTooSmall"; + + result->ret = log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Disk too small for installation, cannot install."); + } else if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent")) { + sysinstall_error_id = "io.systemd.SysInstall.ConflictingDiskLabelPresent"; + + result->ret = log_error_errno( + SYNTHETIC_ERRNO(EHWPOISON), + "A conflicting disk label is already present on the target disk, cannot install unless disk is erased."); + } + + if (sysinstall_error_id && result->context->link) { + r = sd_varlink_errorbo( + result->context->link, + sysinstall_error_id, + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("currentSizeBytes", p.current_size, UINT64_MAX), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("needFreeBytes", p.need_free, UINT64_MAX), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("minimalSizeBytes", p.min_size, UINT64_MAX)); + + if (r < 0) + return result->ret = r; + + return result->ret; + } + + r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */ + if (r != -EBADR) + return result->ret = log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m"); + + return result->ret = log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %s", error_id); + } + + if ((p.progress != UINT_MAX || p.object) && result->context->link) + (void) sysinstall_context_notify(result->context, PROGRESS_INSTALL_PARTITIONS, p.object, p.progress); + + return result->ret = 0; +} + +static int sysinstall_context_invoke_repart_run(SysInstallContext *context) { + + int r; + + assert(context); + + r = connect_to_repart(&context->repart_link); + if (r < 0) + return r; + + /* Seeding the partitions might be very slow, disable timeout */ + r = sd_varlink_set_relative_timeout(context->repart_link, UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Failed to disable IPC timeout: %m"); + + RepartResult result = { + .context = context, + }; + + sd_varlink_set_userdata(context->repart_link, &result); + + r = sd_varlink_bind_reply(context->repart_link, handle_repart_reply); + if (r < 0) + return log_error_errno(r, "Failed to bind repart reply callback: %m"); + + r = sd_varlink_observebo( + context->repart_link, + "io.systemd.Repart.Run", + SD_JSON_BUILD_PAIR_STRING("node", context->node), + SD_JSON_BUILD_PAIR_STRING("empty", context->erase ? "force" : "allow"), + SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", false), + SD_JSON_BUILD_PAIR_CONDITION(!!context->definitions, "definitions", SD_JSON_BUILD_STRV(context->definitions)), + SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true), + SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true)); + if (r < 0) + return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m"); + + for (;;) { + r = sd_varlink_is_idle(context->repart_link); + if (r < 0) + return log_error_errno(r, "Failed to check if varlink connection is idle: %m"); + if (r > 0) + break; + + r = sd_varlink_process(context->repart_link); + if (r < 0) + return log_error_errno(r, "Failed to process varlink connection: %m"); + if (r != 0) + continue; + + r = sd_varlink_wait(context->repart_link, USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "Failed to wait for varlink connection events: %m"); + } + + sd_varlink_set_userdata(context->repart_link, NULL); + + r = sd_varlink_bind_reply(context->repart_link, NULL); + if (r < 0) + return log_error_errno(r, "Failed to unbind repart reply callback: %m"); + + return result.ret; +} + static int read_space_metrics( sd_json_variant *v, uint64_t *min_size, @@ -447,6 +732,7 @@ static int invoke_repart( const char *node, bool erase, bool dry_run, + char **definitions, uint64_t *min_size, /* initialized both on success and error */ uint64_t *current_size, /* ditto */ uint64_t *need_free) { /* ditto */ @@ -481,7 +767,7 @@ static int invoke_repart( SD_JSON_BUILD_PAIR_STRING("node", node), SD_JSON_BUILD_PAIR_STRING("empty", erase ? "force" : "allow"), SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", dry_run), - SD_JSON_BUILD_PAIR_CONDITION(!!arg_definitions, "definitions", SD_JSON_BUILD_STRV(arg_definitions)), + SD_JSON_BUILD_PAIR_CONDITION(!!definitions, "definitions", SD_JSON_BUILD_STRV(definitions)), SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true), SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true)); if (r < 0) { @@ -641,6 +927,7 @@ static int validate_run(sd_varlink **repart_link, const char *node) { node, try_erase, /* dry_run= */ true, + arg_definitions, &min_size, ¤t_size, &need_free); @@ -690,12 +977,9 @@ static int validate_run(sd_varlink **repart_link, const char *node) { } } -static int show_summary(void) { +static int sysinstall_context_show_summary(SysInstallContext *context) { int r; - if (!arg_summary) - return 0; - printf("\n" "%sSummary:%s\n", ansi_underline(), ansi_normal()); @@ -706,12 +990,12 @@ static int show_summary(void) { r = table_add_many( table, TABLE_FIELD, "Selected Disk", - TABLE_STRING, arg_node, + TABLE_STRING, context->node, TABLE_FIELD, "Erase Disk", - TABLE_BOOLEAN, arg_erase, - TABLE_SET_COLOR, arg_erase ? ansi_highlight_red() : NULL, + TABLE_BOOLEAN, context->erase, + TABLE_SET_COLOR, context->erase ? ansi_highlight_red() : NULL, TABLE_FIELD, "Register in Firmware", - TABLE_BOOLEAN, arg_touch_variables); + TABLE_BOOLEAN, context->touch_variables); if (r < 0) return table_log_add_error(r); @@ -724,7 +1008,7 @@ static int show_summary(void) { }; STRV_FOREACH_PAIR(id, text, map) { - MachineCredential *c = machine_credential_find(&arg_credentials, *id); + MachineCredential *c = machine_credential_find(&context->credentials, *id); if (!c) continue; @@ -741,7 +1025,7 @@ static int show_summary(void) { } unsigned n_extra_credentials = 0; - FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + FOREACH_ARRAY(cred, context->credentials.credentials, context->credentials.n_credentials) { bool covered = false; STRV_FOREACH_PAIR(id, text, map) @@ -879,6 +1163,7 @@ static int connect_to_bootctl(sd_varlink **link) { static int invoke_bootctl_install( sd_varlink **link, + bool variables, const char *root_dir, int root_fd) { int r; @@ -904,7 +1189,7 @@ static int invoke_bootctl_install( SD_JSON_BUILD_PAIR_STRING("operation", "new"), SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", fd_idx), SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir), - SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", arg_touch_variables)); + SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", variables)); if (r < 0) return r; @@ -915,12 +1200,16 @@ static int invoke_bootctl_link( sd_varlink **link, const char *root_dir, int root_fd, + const char *kernel_filename, + int kernel_fd, char **encrypted_credentials) { int r; assert(link); assert(root_dir); assert(root_fd >= 0); + assert(kernel_filename); + assert(kernel_fd >= 0); r = connect_to_bootctl(link); if (r < 0) @@ -944,25 +1233,6 @@ static int invoke_bootctl_link( if (root_fd_idx < 0) return log_error_errno(root_fd_idx, "Failed to submit root fd onto Varlink connection: %m"); - _cleanup_free_ char *kernel_filename = NULL; - _cleanup_close_ int kernel_fd = -EBADF; - if (arg_kernel_image) { - r = path_extract_filename(arg_kernel_image, &kernel_filename); - if (r < 0) - return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", arg_kernel_image); - if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", arg_kernel_image); - - kernel_fd = xopenat_full(XAT_FDROOT, arg_kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID); - if (kernel_fd < 0) - return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", arg_kernel_image); - - } else { - r = find_current_kernel(&kernel_filename, &kernel_fd); - if (r < 0) - return r; - } - int kernel_fd_idx = sd_varlink_push_dup_fd(*link, kernel_fd); if (kernel_fd_idx < 0) return log_error_errno(kernel_fd_idx, "Failed to submit kernel fd onto Varlink connection: %m"); @@ -1017,14 +1287,11 @@ static int maybe_reboot(void) { return 0; } -static int read_credential_locale(void) { +static int read_credential_locale(MachineCredentialContext *credentials) { int r; - if (!arg_copy_locale) - return 0; - - if (machine_credential_find(&arg_credentials, "firstboot.locale") || - machine_credential_find(&arg_credentials, "firstboot.locale-messages")) + if (machine_credential_find(credentials, "firstboot.locale") || + machine_credential_find(credentials, "firstboot.locale-messages")) return 0; /* For the main locale we check the two env vars, and if neither is there, we use LC_NUMERIC, since @@ -1032,14 +1299,14 @@ static int read_credential_locale(void) { * separate setting after all */ const char *l = getenv("LC_ALL") ?: getenv("LANG") ?: setlocale(LC_NUMERIC, NULL); if (l) { - r = machine_credential_add(&arg_credentials, "firstboot.locale", l, /* size= */ SIZE_MAX); + r = machine_credential_add(credentials, "firstboot.locale", l, /* size= */ SIZE_MAX); if (r < 0) return log_oom(); } const char *m = setlocale(LC_MESSAGES, NULL); if (m && !streq_ptr(m, l)) { - r = machine_credential_add(&arg_credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX); + r = machine_credential_add(credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX); if (r < 0) return log_oom(); } @@ -1047,13 +1314,10 @@ static int read_credential_locale(void) { return 0; } -static int read_credential_keymap(void) { +static int read_credential_keymap(MachineCredentialContext *credentials) { int r; - if (!arg_copy_keymap) - return 0; - - if (machine_credential_find(&arg_credentials, "firstboot.keymap")) + if (machine_credential_find(credentials, "firstboot.keymap")) return 0; _cleanup_free_ char *keymap = NULL; @@ -1065,7 +1329,7 @@ static int read_credential_keymap(void) { return log_error_errno(r, "Failed to parse '%s': %m", etc_vconsole_conf()); if (!isempty(keymap)) { - r = machine_credential_add(&arg_credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX); + r = machine_credential_add(credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX); if (r < 0) return log_oom(); } @@ -1073,13 +1337,10 @@ static int read_credential_keymap(void) { return 0; } -static int read_credential_timezone(void) { +static int read_credential_timezone(MachineCredentialContext *credentials) { int r; - if (!arg_copy_timezone) - return 0; - - if (machine_credential_find(&arg_credentials, "firstboot.timezone")) + if (machine_credential_find(credentials, "firstboot.timezone")) return 0; _cleanup_free_ char *tz = NULL; @@ -1087,7 +1348,7 @@ static int read_credential_timezone(void) { if (r < 0) log_warning_errno(r, "Failed to read timezone, skipping timezone propagation: %m"); else { - r = machine_credential_add(&arg_credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX); + r = machine_credential_add(credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX); if (r < 0) return log_oom(); } @@ -1095,20 +1356,26 @@ static int read_credential_timezone(void) { return 0; } -static int read_credentials(void) { +static int sysinstall_context_read_credentials(SysInstallContext *context) { int r; - r = read_credential_locale(); - if (r < 0) - return r; + if (context->copy_locale) { + r = read_credential_locale(&context->credentials); + if (r < 0) + return r; + } - r = read_credential_keymap(); - if (r < 0) - return r; + if (context->copy_keymap) { + r = read_credential_keymap(&context->credentials); + if (r < 0) + return r; + } - r = read_credential_timezone(); - if (r < 0) - return r; + if (context->copy_timezone) { + r = read_credential_timezone(&context->credentials); + if (r < 0) + return r; + } return 0; } @@ -1179,13 +1446,13 @@ static int encrypt_one_credential(sd_varlink **link, const MachineCredential *in return 0; } -static int encrypt_credentials(sd_varlink **link, char ***encrypted) { +static int encrypt_credentials(sd_varlink **link, MachineCredentialContext *credentials, char ***encrypted) { int r; assert(link); assert(encrypted); - FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + FOREACH_ARRAY(cred, credentials->credentials, credentials->n_credentials) { r = encrypt_one_credential(link, cred, encrypted); if (r < 0) return r; @@ -1206,11 +1473,22 @@ static const ImagePolicy image_policy = { .default_flags = PARTITION_POLICY_IGNORE, }; -static int settle_definitions(void) { +static int settle_definitions(char **definitions, char ***ret_definitions) { + + _cleanup_strv_free_ char **d = NULL; int r; - if (arg_definitions) + assert(ret_definitions); + + if (definitions) { + d = strv_copy(definitions); + if (!d) + return log_oom(); + + *ret_definitions = TAKE_PTR(d); + return 0; + } /* If /usr/lib/repart.sysinstall.d/ is populated, use it, otherwise use the regular definition * files */ @@ -1226,14 +1504,552 @@ static int settle_definitions(void) { return log_error_errno(r, "Failed to enumerate *.conf files: %m"); if (!strv_isempty(files)) { - arg_definitions = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d")); - if (!arg_definitions) + d = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d")); + if (!d) return log_oom(); + + *ret_definitions = TAKE_PTR(d); + } + + return 0; +} + +static int sysinstall_context_settle_definitions(SysInstallContext *context, + char **definitions) { + + return settle_definitions(definitions, &context->definitions); +} + +static int sysinstall_context_settle_kernel_image(SysInstallContext *context, + const char *kernel_image) { + + _cleanup_free_ char *kernel_filename = NULL; + _cleanup_close_ int kernel_fd = -EBADF; + int r; + + assert(context->kernel_fd < 0); + assert(!context->kernel_filename); + + if (kernel_image) { + r = path_extract_filename(kernel_image, &kernel_filename); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel_image); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", kernel_image); + + kernel_fd = xopenat_full(XAT_FDROOT, kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID); + if (kernel_fd < 0) + return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", kernel_image); + + } else { + r = find_current_kernel(&kernel_filename, &kernel_fd); + if (r < 0) + return r; + } + + context->kernel_filename = TAKE_PTR(kernel_filename); + context->kernel_fd = TAKE_FD(kernel_fd); + + return 0; +} + +static int sysinstall_context_run(SysInstallContext *context) { + + int r; + + assert(context); + + (void) sysinstall_context_notify(context, PROGRESS_ENCRYPT_CREDENTIALS, NULL, UINT_MAX); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL; + _cleanup_strv_free_ char **encrypted_credentials = NULL; + r = encrypt_credentials(&creds_link, &context->credentials, &encrypted_credentials); + if (r < 0) + return r; + + (void) sysinstall_context_notify(context, PROGRESS_INSTALL_PARTITIONS, NULL, UINT_MAX); + + /* Do the main part of the installation */ + + r = sysinstall_context_invoke_repart_run(context); + if (r < 0) + return r; + + (void) sysinstall_context_notify(context, PROGRESS_MOUNT_PARTITIONS, NULL, UINT_MAX); + + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(umount_and_freep) char *root_dir = NULL; + _cleanup_close_ int root_fd = -EBADF; + r = mount_image_privately_interactively( + context->node, + &image_policy, + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY | + DISSECT_IMAGE_DISCARD_ANY | + DISSECT_IMAGE_GPT_ONLY | + DISSECT_IMAGE_FSCK | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_PIN_PARTITION_DEVICES, + &root_dir, + &root_fd, + &loop_device); + if (r < 0) + return log_error_errno(r, "Failed to mount new image: %m"); + + (void) sysinstall_context_notify(context, PROGRESS_INSTALL_KERNEL, NULL, UINT_MAX); + + _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL; + r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, context->kernel_filename, context->kernel_fd, encrypted_credentials); + if (r < 0) + return r; + + (void) sysinstall_context_notify(context, PROGRESS_INSTALL_BOOTLOADER, NULL, UINT_MAX); + + r = invoke_bootctl_install(&bootctl_link, context->touch_variables, root_dir, root_fd); + if (r < 0) + return r; + + (void) sysinstall_context_notify(context, PROGRESS_UNMOUNT_PARTITIONS, NULL, UINT_MAX); + + root_fd = safe_close(root_fd); + r = umount_recursive(root_dir, /* flags= */ 0); + if (r < 0) + log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m"); + loop_device = loop_device_unref(loop_device); + sync(); + + return 0; +} + +typedef struct ListCandidateDevicesContext { + char **definitions; + bool subscribe; + + sd_varlink *repart_link; /* A repart connection to get candidate devices */ + sd_varlink *dry_run_repart_link; /* A second repart connection to perform a dry run on each node */ + + sd_varlink *link; +} ListCandidateDevicesContext; + +static ListCandidateDevicesContext* list_candidate_devices_context_new(void) { + ListCandidateDevicesContext *context = new(ListCandidateDevicesContext, 1); + + if (!context) + return NULL; + + *context = (ListCandidateDevicesContext) {}; + + return context; +} + +static ListCandidateDevicesContext* list_candidate_devices_context_free(ListCandidateDevicesContext *context) { + if (!context) + return NULL; + + strv_free(context->definitions); + + context->repart_link = sd_varlink_flush_close_unref(context->repart_link); + context->dry_run_repart_link = sd_varlink_flush_close_unref(context->dry_run_repart_link); + context->link = sd_varlink_unref(context->link); + + return mfree(context); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(ListCandidateDevicesContext*, list_candidate_devices_context_free); + +static int list_candidate_devices_context_settle_definitions(ListCandidateDevicesContext *context, + char **definitions) { + + return settle_definitions(definitions, &context->definitions); +} + +static void vl_on_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) { + assert(server); + assert(link); + + list_candidate_devices_context_free(sd_varlink_set_userdata(link, NULL)); +} + +typedef struct DevicesResponse { + const char *node; + char **symlinks; + uint64_t diskseq; + uint64_t size; + const char *model; + const char *vendor; + const char *subsystem; + const char *action; +} DevicesResponse; + +static void devices_response_done(DevicesResponse *p) { + assert(p); + + p->symlinks = strv_free(p->symlinks); +} + +static int fetch_candidate_devices_reply( + sd_varlink *repart_link, + sd_json_variant *reply, + const char *error_id, + sd_varlink_reply_flags_t flags, + void *userdata) { + + int r; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + ListCandidateDevicesContext *context = ASSERT_PTR(userdata); + + if (error_id) { + if (streq(error_id, "io.systemd.Repart.NoCandidateDevices")) + return sd_varlink_error(context->link, "io.systemd.SysInstall.NoCandidateDevices", NULL); + + return sd_varlink_error(context->link, error_id, NULL); + } + + static const sd_json_dispatch_field dispatch_table[] = { + { "node", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, node), 0 }, + { "symlinks", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(DevicesResponse, symlinks), 0 }, + { "diskseq", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(DevicesResponse, diskseq), 0 }, + { "sizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(DevicesResponse, size), 0 }, + { "model", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, model), 0 }, + { "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, vendor), 0 }, + { "subsystem", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, subsystem), 0 }, + { "action", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, action), 0 }, + {} + }; + + _cleanup_(devices_response_done) DevicesResponse p = { + .diskseq = UINT64_MAX, + .size = UINT64_MAX, + }; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return r; + + if (context->subscribe) { + /* The action needs to be ready, remove or add else we don't support the action */ + if (streq(p.action, "ready")) + return sd_varlink_notifybo(context->link, SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("ready"))); + + if (streq(p.action, "remove")) + return sd_varlink_notifybo(context->link, + SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("remove")), + SD_JSON_BUILD_PAIR_STRING("node", p.node)); + + if (!streq(p.action, "add")) { + log_debug("Skip unsupported action '%s' while fetching candidate devices.", p.action); + return 0; + } + } + + uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX; + r = invoke_repart( + &context->dry_run_repart_link, + p.node, + /* erase= */ false, + /* dry_run= */ true, + context->definitions, + &min_size, + ¤t_size, + &need_free); + + DeviceFit fit; + if (r < 0) { + if (r == -ENOSPC) + fit = DEVICE_FIT_INSUFFICIENT_FREE_SPACE; + else if (r == -E2BIG) + fit = DEVICE_FIT_DISK_TOO_SMALL; + else if (r == -EHWPOISON) + fit = DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT; + else + return r; + } else + fit = DEVICE_FIT_ENOUGH_FREE_SPACE; + + r = sd_json_buildo(&v, + SD_JSON_BUILD_PAIR_STRING("node", p.node), + JSON_BUILD_PAIR_STRV_NON_EMPTY("symlinks", p.symlinks), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("diskseq", p.diskseq, UINT64_MAX), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", p.size, UINT64_MAX), + JSON_BUILD_PAIR_STRING_NON_EMPTY("model", p.model), + JSON_BUILD_PAIR_STRING_NON_EMPTY("vendor", p.vendor), + JSON_BUILD_PAIR_STRING_NON_EMPTY("subsystem", p.subsystem), + JSON_BUILD_PAIR_ENUM("fit", device_fit_to_string(fit)), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("currentSizeBytes", current_size, UINT64_MAX), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("needFreeBytes", need_free, UINT64_MAX), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("minimalSizeBytes", min_size, UINT64_MAX), + SD_JSON_BUILD_PAIR_CONDITION(context->subscribe, "action", JSON_BUILD_CONST_STRING("add"))); + if (r < 0) + return r; + + if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) + return sd_varlink_notify(context->link, v); + + return sd_varlink_reply(context->link, v); +} + +typedef struct ListCandidateDevicesParameters { + char **definitions; + bool subscribe; +} ListCandidateDevicesParameters; + +static void list_candidate_devices_parameters_done(ListCandidateDevicesParameters *p) { + assert(p); + + p->definitions = strv_free(p->definitions); +} + +static int vl_method_list_candidate_devices( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + int r; + + assert(link); + + sd_varlink_server *varlink_server = sd_varlink_get_server(link); + sd_event *event = sd_varlink_server_get_event(varlink_server); + Hashmap **polkit_registry = ASSERT_PTR(sd_varlink_server_get_userdata(varlink_server)); + + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.sysinstall.ListCandidateDevices", + /* details= */ NULL, + polkit_registry); + if (r <= 0) + return r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "definitions", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_path, offsetof(ListCandidateDevicesParameters, definitions), SD_JSON_STRICT }, + { "subscribe", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(ListCandidateDevicesParameters, subscribe), 0 }, + {} + }; + + _cleanup_(list_candidate_devices_parameters_done) ListCandidateDevicesParameters p = {}; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + _cleanup_(list_candidate_devices_context_freep) ListCandidateDevicesContext* context = list_candidate_devices_context_new(); + if (!context) + return log_oom(); + + context->subscribe = p.subscribe; + r = list_candidate_devices_context_settle_definitions(context, p.definitions); + if (r < 0) + return r; + + context->link = sd_varlink_ref(link); + + r = connect_to_repart(&context->repart_link); + if (r < 0) + return r; + + r = sd_varlink_attach_event(context->repart_link, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno( + r, + "Failed to attach io.systemd.Repart.ListCandidateDevices() varlink connection to event loop: %m"); + + r = sd_varlink_bind_reply(context->repart_link, fetch_candidate_devices_reply); + if (r < 0) + return r; + + r = sd_varlink_observebo( + context->repart_link, + "io.systemd.Repart.ListCandidateDevices", + SD_JSON_BUILD_PAIR_BOOLEAN("subscribe", context->subscribe), + SD_JSON_BUILD_PAIR_BOOLEAN("ignoreRoot", true)); + + if (r < 0) + return log_error_errno( + r, + "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m"); + + r = sd_varlink_server_bind_disconnect(varlink_server, vl_on_disconnect); + if (r < 0) + return r; + + /* The context is freed in vl_on_disconnect() */ + sd_varlink_set_userdata(context->repart_link, context); + sd_varlink_set_userdata(link, TAKE_PTR(context)); + + return 0; +} + +typedef struct RunParameters { + char *node; + char **definitions; + bool erase; + bool variables; + char *kernel_image; + bool copy_locale; + bool copy_keymap; + bool copy_timezone; + sd_json_variant *credentials; +} RunParameters; + +static void run_parameters_done(RunParameters *p) { + assert(p); + + p->node = mfree(p->node); + p->definitions = strv_free(p->definitions); + p->kernel_image = mfree(p->kernel_image); + p->credentials = sd_json_variant_unref(p->credentials); +} + +typedef struct CredentialParameters { + const char *id; + struct iovec value; +} CredentialParameters; + +static void credential_parameters_done(CredentialParameters *p) { + assert(p); + + iovec_done_erase(&p->value); +} + +static int credentials_from_json_array(MachineCredentialContext *credentials, sd_json_variant *v) { + + int r; + sd_json_variant *credential; + + assert(credentials); + + static const sd_json_dispatch_field dispatch_table[] = { + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialParameters, id), SD_JSON_MANDATORY }, + { "value", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialParameters, value), SD_JSON_MANDATORY }, + {} + }; + + JSON_VARIANT_ARRAY_FOREACH(credential, v) { + _cleanup_(credential_parameters_done) CredentialParameters p = {}; + + r = sd_json_dispatch(credential, dispatch_table, /* flags= */ 0, &p); + if (r < 0) + return r; + + r = machine_credential_add(credentials, p.id, p.value.iov_base, p.value.iov_len); + if (r < 0) + return r; } return 0; } +static int vl_method_run( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "node", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(RunParameters, node), SD_JSON_MANDATORY | SD_JSON_STRICT }, + { "definitions", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_path, offsetof(RunParameters, definitions), SD_JSON_NULLABLE | SD_JSON_STRICT }, + { "erase", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, erase), SD_JSON_MANDATORY }, + { "variables", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, variables), SD_JSON_NULLABLE }, + { "kernelImagePath", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(RunParameters, kernel_image), SD_JSON_NULLABLE | SD_JSON_STRICT }, + { "copyLocale", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_locale), SD_JSON_NULLABLE }, + { "copyKeymap", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_keymap), SD_JSON_NULLABLE }, + { "copyTimezone", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_timezone), SD_JSON_NULLABLE }, + { "credentials", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(RunParameters, credentials), SD_JSON_NULLABLE }, + {} + }; + + int r; + + assert(link); + + sd_varlink_server *varlink_server = sd_varlink_get_server(link); + Hashmap **polkit_registry = ASSERT_PTR(sd_varlink_server_get_userdata(varlink_server)); + + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.sysinstall.Run", + /* details= */ NULL, + polkit_registry); + if (r <= 0) + return r; + + _cleanup_(run_parameters_done) RunParameters p = {}; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + _cleanup_(sysinstall_context_done) SysInstallContext context = (SysInstallContext) { + .copy_locale = p.copy_locale, + .copy_keymap = p.copy_keymap, + .copy_timezone = p.copy_timezone, + .erase = p.erase, + .touch_variables = p.variables, + .node = TAKE_PTR(p.node), + .kernel_fd = -EBADF, + }; + + r = sysinstall_context_settle_definitions(&context, p.definitions); + if (r < 0) + return r; + + if (FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + context.link = sd_varlink_ref(link); + + r = credentials_from_json_array(&context.credentials, p.credentials); + if (r < 0) + return r; + + r = sysinstall_context_read_credentials(&context); + if (r < 0) + return r; + + r = sysinstall_context_settle_kernel_image(&context, p.kernel_image); + if (r < 0) + return r; + + r = sysinstall_context_run(&context); + if (r < 0) + return r; + + return sd_varlink_reply(link, NULL); +} + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL; + int r; + + /* Invocation as Varlink service */ + + r = varlink_server_new( + &varlink_server, + 0, + /* userdata= */ &polkit_registry); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysInstall); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method_many( + varlink_server, + "io.systemd.SysInstall.ListCandidateDevices", vl_method_list_candidate_devices, + "io.systemd.SysInstall.Run", vl_method_run); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = sd_varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + static void end_marker(void) { if (!arg_welcome) @@ -1254,7 +2070,18 @@ static int run(int argc, char *argv[]) { log_setup(); - r = settle_definitions(); + if (arg_varlink) + return vl_server(); + + _cleanup_(sysinstall_context_done) SysInstallContext context = (SysInstallContext) { + .copy_locale = arg_copy_locale, + .copy_keymap = arg_copy_keymap, + .copy_timezone = arg_copy_timezone, + .credentials = TAKE_STRUCT(arg_credentials), + .kernel_fd = -EBADF, + }; + + r = sysinstall_context_settle_definitions(&context, arg_definitions); if (r < 0) return r; @@ -1289,6 +2116,7 @@ static int run(int argc, char *argv[]) { /* node= */ NULL, /* erase= */ true, /* dry_run= */ true, + arg_definitions, &min_size, /* current_size= */ NULL, /* need_free= */ NULL); @@ -1322,7 +2150,11 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = read_credentials(); + r = sysinstall_context_read_credentials(&context); + if (r < 0) + return r; + + r = sysinstall_context_settle_kernel_image(&context, arg_kernel_image); if (r < 0) return r; @@ -1331,9 +2163,15 @@ static int run(int argc, char *argv[]) { assert(arg_erase >= 0); assert(arg_touch_variables >= 0); - r = show_summary(); - if (r < 0) - return r; + context.node = TAKE_PTR(arg_node); + context.touch_variables = arg_touch_variables; + context.erase = arg_erase; + + if (arg_summary) { + r = sysinstall_context_show_summary(&context); + if (r < 0) + return r; + } r = prompt_confirm(); if (r < 0) @@ -1341,79 +2179,10 @@ static int run(int argc, char *argv[]) { putchar('\n'); - log_notice("%s%sEncrypting credentials...", - emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", emoji_enabled() ? " " : ""); - - _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL; - _cleanup_strv_free_ char **encrypted_credentials = NULL; - r = encrypt_credentials(&creds_link, &encrypted_credentials); - if (r < 0) - return r; - - log_notice("%s%sInstalling partitions...", - emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); - - /* Do the main part of the installation */ - r = invoke_repart( - &repart_link, - arg_node, - arg_erase, - /* dry_run= */ false, - /* min_size= */ NULL, - /* current_size= */ NULL, - /* need_free= */ NULL); - if (r < 0) - return r; - - log_notice("%s%sMounting partitions...", - emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); - - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_freep) char *root_dir = NULL; - _cleanup_close_ int root_fd = -EBADF; - r = mount_image_privately_interactively( - arg_node, - &image_policy, - DISSECT_IMAGE_REQUIRE_ROOT | - DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_ALLOW_USERSPACE_VERITY | - DISSECT_IMAGE_DISCARD_ANY | - DISSECT_IMAGE_GPT_ONLY | - DISSECT_IMAGE_FSCK | - DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES, - &root_dir, - &root_fd, - &loop_device); - if (r < 0) - return log_error_errno(r, "Failed to mount new image: %m"); - - log_notice("%s%sInstalling kernel...", - emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); - - _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL; - r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, encrypted_credentials); + r = sysinstall_context_run(&context); if (r < 0) return r; - log_notice("%s%sInstalling boot loader...", - emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); - - r = invoke_bootctl_install(&bootctl_link, root_dir, root_fd); - if (r < 0) - return r; - - log_notice("%s%sUnmounting partitions...", - emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : ""); - - root_fd = safe_close(root_fd); - r = umount_recursive(root_dir, /* flags= */ 0); - if (r < 0) - log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m"); - loop_device = loop_device_unref(loop_device); - sync(); - log_notice("%s%sInstallation succeeded.", emoji_enabled() ? glyph(GLYPH_SPARKLES) : "", emoji_enabled() ? " " : ""); diff --git a/src/systemctl/meson.build b/src/systemctl/meson.build index 882704c9d7d87..c287f8a490384 100644 --- a/src/systemctl/meson.build +++ b/src/systemctl/meson.build @@ -65,6 +65,9 @@ executables += [ 'sources' : files('fuzz-systemctl-parse-argv.c'), 'objects' : ['systemctl'], 'link_with' : systemctl_link_with, + 'dependencies' : [ + libselinux_cflags, + ], }, ] diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index b6f9436606568..ec873269047e8 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -3,6 +3,7 @@ systemd_sysupdate_sources = files( 'sysupdate-cache.c', 'sysupdate-cleanup.c', + 'sysupdate-config.c', 'sysupdate-feature.c', 'sysupdate-instance.c', 'sysupdate-partition.c', diff --git a/src/sysupdate/sysupdate-config.c b/src/sysupdate/sysupdate-config.c new file mode 100644 index 0000000000000..7f553a95f0b41 --- /dev/null +++ b/src/sysupdate/sysupdate-config.c @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "log.h" +#include "specifier.h" +#include "string-util.h" +#include "strv.h" +#include "sysupdate-config.h" +#include "web-util.h" + +int config_parse_url_specifiers( + 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) { + + const char *root = userdata; + char **s = ASSERT_PTR(data); + int r; + + assert(rvalue); + + if (isempty(rvalue)) { + *s = mfree(*s); + return 0; + } + + _cleanup_free_ char *resolved = NULL; + r = specifier_printf(rvalue, NAME_MAX, system_and_tmp_specifier_table, root, NULL, &resolved); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (!http_url_is_valid(resolved)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s= URL is not valid, ignoring: %s", lvalue, rvalue); + return 0; + } + + return free_and_replace(*s, resolved); +} + +int config_parse_url_specifiers_many( + 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) { + + const char *root = userdata; + char ***s = ASSERT_PTR(data); + int r; + + assert(rvalue); + + if (isempty(rvalue)) { + *s = strv_free(*s); + return 0; + } + + _cleanup_free_ char *resolved = NULL; + r = specifier_printf(rvalue, NAME_MAX, system_and_tmp_specifier_table, root, NULL, &resolved); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (!http_url_is_valid(resolved)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "%s= URL is not valid, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (strv_consume(s, TAKE_PTR(resolved)) < 0) + return log_oom(); + + return 0; +} diff --git a/src/sysupdate/sysupdate-config.h b/src/sysupdate/sysupdate-config.h new file mode 100644 index 0000000000000..bd972d598d56a --- /dev/null +++ b/src/sysupdate/sysupdate-config.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser-forward.h" + +CONFIG_PARSER_PROTOTYPE(config_parse_url_specifiers); +CONFIG_PARSER_PROTOTYPE(config_parse_url_specifiers_many); diff --git a/src/sysupdate/sysupdate-feature.c b/src/sysupdate/sysupdate-feature.c index 0c9bb112fe9b7..49c2e77988b93 100644 --- a/src/sysupdate/sysupdate-feature.c +++ b/src/sysupdate/sysupdate-feature.c @@ -5,9 +5,8 @@ #include "hash-funcs.h" #include "path-util.h" #include "string-util.h" -#include "sysupdate.h" +#include "sysupdate-config.h" #include "sysupdate-feature.h" -#include "web-util.h" static Feature *feature_free(Feature *f) { if (!f) @@ -42,45 +41,6 @@ DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(feature_hash_ops, char, string_hash_func, string_compare_func, Feature, feature_unref); -static int config_parse_url_specifiers( - 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) { - char **s = ASSERT_PTR(data); - const char *root = ASSERT_PTR(userdata); - _cleanup_free_ char *resolved = NULL; - int r; - - assert(rvalue); - - if (isempty(rvalue)) { - *s = mfree(*s); - return 0; - } - - r = specifier_printf(rvalue, NAME_MAX, specifier_table, root, NULL, &resolved); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue); - return 0; - } - - if (!http_url_is_valid(resolved)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "%s= URL is not valid, ignoring: %s", lvalue, rvalue); - return 0; - } - - return free_and_replace(*s, resolved); -} - int feature_read_definition(Feature *f, const char *root, const char *path, const char *const *dirs) { assert(f); diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index c79743cad6908..2a29448836aa7 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -36,6 +36,7 @@ #include "sync-util.h" #include "sysupdate.h" #include "sysupdate-cleanup.h" +#include "sysupdate-config.h" #include "sysupdate-feature.h" #include "sysupdate-instance.h" #include "sysupdate-pattern.h" @@ -127,7 +128,7 @@ static int config_parse_protect_version( assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue); @@ -166,7 +167,7 @@ static int config_parse_min_version( assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue); @@ -182,7 +183,7 @@ static int config_parse_min_version( return free_and_replace(*version, resolved); } -static int config_parse_url_specifiers( +static int config_parse_transfer_url_specifiers_many( const char *unit, const char *filename, unsigned line, @@ -193,36 +194,13 @@ static int config_parse_url_specifiers( const char *rvalue, void *data, void *userdata) { - char ***s = ASSERT_PTR(data); - Transfer *t = ASSERT_PTR(userdata); - _cleanup_free_ char *resolved = NULL; - int r; - assert(rvalue); - - if (isempty(rvalue)) { - *s = strv_free(*s); - return 0; - } - - r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue); - return 0; - } - - if (!http_url_is_valid(resolved)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "%s= URL is not valid, ignoring: %s", lvalue, rvalue); - return 0; - } + Transfer *t = ASSERT_PTR(userdata); - r = strv_push(s, TAKE_PTR(resolved)); - if (r < 0) - return log_oom(); + /* Here we expect userdata to point to our Transfer object, but config_parse_url_specifiers() wants + * the root dir there, hence fix this up */ - return 0; + return config_parse_url_specifiers_many(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, t->context->root); } static int config_parse_current_symlink( @@ -244,7 +222,7 @@ static int config_parse_current_symlink( assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue); @@ -333,7 +311,7 @@ static int config_parse_resource_pattern( if (r == 0) break; - r = specifier_printf(word, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); + r = specifier_printf(word, NAME_MAX, system_and_tmp_specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue); @@ -377,7 +355,7 @@ static int config_parse_resource_path( return 0; } - r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, t->context->root, NULL, &resolved); + r = specifier_printf(rvalue, PATH_MAX-1, system_and_tmp_specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in Path=, ignoring: %s", rvalue); @@ -510,33 +488,33 @@ int transfer_read_definition(Transfer *t, const char *path, const char **dirs, H assert(t); ConfigTableItem table[] = { - { "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version }, - { "Transfer", "ProtectVersion", config_parse_protect_version, 0, &t->protected_versions }, - { "Transfer", "Verify", config_parse_bool, 0, &t->verify }, - { "Transfer", "ChangeLog", config_parse_url_specifiers, 0, &t->changelog }, - { "Transfer", "AppStream", config_parse_url_specifiers, 0, &t->appstream }, - { "Transfer", "Features", config_parse_strv, 0, &t->features }, - { "Transfer", "RequisiteFeatures", config_parse_strv, 0, &t->requisite_features }, - { "Source", "Type", config_parse_resource_type, 0, &t->source.type }, - { "Source", "Path", config_parse_resource_path, 0, &t->source }, - { "Source", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->source.path_relative_to }, - { "Source", "MatchPattern", config_parse_resource_pattern, 0, &t->source.patterns }, - { "Target", "Type", config_parse_resource_type, 0, &t->target.type }, - { "Target", "Path", config_parse_resource_path, 0, &t->target }, - { "Target", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->target.path_relative_to }, - { "Target", "MatchPattern", config_parse_resource_pattern, 0, &t->target.patterns }, - { "Target", "MatchPartitionType", config_parse_resource_ptype, 0, &t->target }, - { "Target", "PartitionUUID", config_parse_partition_uuid, 0, t }, - { "Target", "PartitionFlags", config_parse_partition_flags, 0, t }, - { "Target", "PartitionNoAuto", config_parse_tristate, 0, &t->no_auto }, - { "Target", "PartitionGrowFileSystem", config_parse_tristate, 0, &t->growfs }, - { "Target", "ReadOnly", config_parse_tristate, 0, &t->read_only }, - { "Target", "Mode", config_parse_mode, 0, &t->mode }, - { "Target", "TriesLeft", config_parse_uint64, 0, &t->tries_left }, - { "Target", "TriesDone", config_parse_uint64, 0, &t->tries_done }, - { "Target", "InstancesMax", config_parse_instances_max, 0, &t->instances_max }, - { "Target", "RemoveTemporary", config_parse_bool, 0, &t->remove_temporary }, - { "Target", "CurrentSymlink", config_parse_current_symlink, 0, &t->current_symlink }, + { "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version }, + { "Transfer", "ProtectVersion", config_parse_protect_version, 0, &t->protected_versions }, + { "Transfer", "Verify", config_parse_bool, 0, &t->verify }, + { "Transfer", "ChangeLog", config_parse_transfer_url_specifiers_many, 0, &t->changelog }, + { "Transfer", "AppStream", config_parse_transfer_url_specifiers_many, 0, &t->appstream }, + { "Transfer", "Features", config_parse_strv, 0, &t->features }, + { "Transfer", "RequisiteFeatures", config_parse_strv, 0, &t->requisite_features }, + { "Source", "Type", config_parse_resource_type, 0, &t->source.type }, + { "Source", "Path", config_parse_resource_path, 0, &t->source }, + { "Source", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->source.path_relative_to }, + { "Source", "MatchPattern", config_parse_resource_pattern, 0, &t->source.patterns }, + { "Target", "Type", config_parse_resource_type, 0, &t->target.type }, + { "Target", "Path", config_parse_resource_path, 0, &t->target }, + { "Target", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->target.path_relative_to }, + { "Target", "MatchPattern", config_parse_resource_pattern, 0, &t->target.patterns }, + { "Target", "MatchPartitionType", config_parse_resource_ptype, 0, &t->target }, + { "Target", "PartitionUUID", config_parse_partition_uuid, 0, t }, + { "Target", "PartitionFlags", config_parse_partition_flags, 0, t }, + { "Target", "PartitionNoAuto", config_parse_tristate, 0, &t->no_auto }, + { "Target", "PartitionGrowFileSystem", config_parse_tristate, 0, &t->growfs }, + { "Target", "ReadOnly", config_parse_tristate, 0, &t->read_only }, + { "Target", "Mode", config_parse_mode, 0, &t->mode }, + { "Target", "TriesLeft", config_parse_uint64, 0, &t->tries_left }, + { "Target", "TriesDone", config_parse_uint64, 0, &t->tries_done }, + { "Target", "InstancesMax", config_parse_instances_max, 0, &t->instances_max }, + { "Target", "RemoveTemporary", config_parse_bool, 0, &t->remove_temporary }, + { "Target", "CurrentSymlink", config_parse_current_symlink, 0, &t->current_symlink }, {} }; diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index e5af39849c573..2a277026b007c 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -34,7 +34,6 @@ #include "pretty-print.h" #include "runtime-scope.h" #include "sort-util.h" -#include "specifier.h" #include "string-util.h" #include "strv.h" #include "sysupdate.h" @@ -74,19 +73,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_component, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep); -const Specifier specifier_table[] = { - COMMON_SYSTEM_SPECIFIERS, - COMMON_TMP_SPECIFIERS, - {} -}; - -#define CONTEXT_NULL \ - (Context) { \ - .sync = true, \ - .instances_max = UINT64_MAX, \ - .verify = -1, \ - .cleanup = -1, \ - .installdb_fd = -EBADF, \ +#define CONTEXT_NULL \ + (Context) { \ + .sync = true, \ + .instances_max = UINT64_MAX, \ + .verify = -1, \ + .cleanup = -1, \ + .installdb_fd = -EBADF, \ .target_identifier.class = _TARGET_CLASS_INVALID, \ } @@ -186,7 +179,50 @@ static void server_done(Server *s) { static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free); -static int read_definitions( +static int read_features( + Context *c, + const char **dirs) { + + int r; + + assert(c); + + ConfFile **files = NULL; + size_t n_files = 0; + CLEANUP_ARRAY(files, n_files, conf_file_free_array); + + r = conf_files_list_strv_full( + ".feature", + c->root, + CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, + dirs, + &files, + &n_files); + if (r < 0) + return log_error_errno(r, "Failed to enumerate sysupdate.d/*.feature definitions: %m"); + + FOREACH_ARRAY(i, files, n_files) { + ConfFile *e = *i; + + _cleanup_(feature_unrefp) Feature *f = feature_new(); + if (!f) + return log_oom(); + + r = feature_read_definition(f, c->root, e->result, dirs); + if (r < 0) + return r; + + r = hashmap_ensure_put(&c->features, &feature_hash_ops, f->id, f); + if (r < 0) + return log_error_errno(r, "Failed to insert feature '%s' into map: %m", f->id); + + TAKE_PTR(f); + } + + return 0; +} + +static int read_transfers( Context *c, const char **dirs, const char *suffix, @@ -279,42 +315,17 @@ static int context_read_definitions(Context *c, const char* node, ReadDefinition if (!dirs) return log_oom(); - ConfFile **files = NULL; - size_t n_files = 0; - - CLEANUP_ARRAY(files, n_files, conf_file_free_array); - - r = conf_files_list_strv_full(".feature", c->root, - CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, - (const char**) dirs, &files, &n_files); + r = read_features(c, (const char**) dirs); if (r < 0) - return log_error_errno(r, "Failed to enumerate sysupdate.d/*.feature definitions: %m"); - - FOREACH_ARRAY(i, files, n_files) { - _cleanup_(feature_unrefp) Feature *f = NULL; - ConfFile *e = *i; - - f = feature_new(); - if (!f) - return log_oom(); - - r = feature_read_definition(f, c->root, e->result, (const char**) dirs); - if (r < 0) - return r; - - r = hashmap_ensure_put(&c->features, &feature_hash_ops, f->id, f); - if (r < 0) - return log_error_errno(r, "Failed to insert feature '%s' into map: %m", f->id); - TAKE_PTR(f); - } + return r; - r = read_definitions(c, (const char**) dirs, ".transfer", node); + r = read_transfers(c, (const char**) dirs, ".transfer", node); if (r < 0) return r; if (c->n_transfers + c->n_disabled_transfers == 0) { /* Backwards-compat: If no .transfer defs are found, fall back to trying .conf! */ - r = read_definitions(c, (const char**) dirs, ".conf", node); + r = read_transfers(c, (const char**) dirs, ".conf", node); if (r < 0) return r; @@ -2155,11 +2166,12 @@ static int context_list_components(Context *context, char ***ret_component_names /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a * TARGET_HOST? See target_get_argument() in sysupdated.c */ if (ret_has_default_component) - *ret_has_default_component = (!context->definitions && - !context->component && - !context->root && - !context->image && - context->n_transfers > 0); + *ret_has_default_component = + !context->definitions && + !context->component && + !context->root && + !context->image && + context->n_transfers > 0; return 0; } diff --git a/src/sysupdate/sysupdate.h b/src/sysupdate/sysupdate.h index 02a9f7c8f348c..3dcaae527d196 100644 --- a/src/sysupdate/sysupdate.h +++ b/src/sysupdate/sysupdate.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "specifier.h" #include "sysupdate-forward.h" #include "sysupdate-target.h" @@ -46,5 +45,3 @@ typedef struct Context { } Context; void context_done(Context *c); - -extern const Specifier specifier_table[]; diff --git a/src/test/gcrypt-util.h b/src/test/gcrypt-util.h index 03404886974ce..37eb3f5b61922 100644 --- a/src/test/gcrypt-util.h +++ b/src/test/gcrypt-util.h @@ -11,6 +11,10 @@ int dlopen_gcrypt(int log_level); int initialize_libgcrypt(bool secmem); #if HAVE_GCRYPT +#ifndef SYSTEMD_CFLAGS_MARKER_LIBGCRYPT +# error "missing libgcrypt_cflags in meson dependency." +#endif + #define GCRYPT_NOTE(priority) \ SD_ELF_NOTE_DLOPEN("gcrypt", \ "Support for journald forward-sealing", \ diff --git a/src/test/meson.build b/src/test/meson.build index c90235e078949..d7353ce8e2ce5 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -79,13 +79,11 @@ simple_tests += files( 'test-clock.c', 'test-color-util.c', 'test-compare-operator.c', - 'test-condition.c', 'test-conf-files.c', 'test-conf-parser.c', 'test-copy.c', 'test-coredump-util.c', 'test-cpu-set-util.c', - 'test-creds.c', 'test-daemon.c', 'test-data-fd-util.c', 'test-date.c', @@ -170,7 +168,6 @@ simple_tests += files( 'test-parse-util.c', 'test-path-lookup.c', 'test-path-util.c', - 'test-pe-binary.c', 'test-percent-util.c', 'test-pidref.c', 'test-pressure.c', @@ -246,6 +243,7 @@ executables += [ test_template + { 'sources' : files('test-acl-util.c'), 'conditions' : ['HAVE_ACL'], + 'dependencies' : libacl_cflags, }, test_template + { 'sources' : files('test-af-list.c') + @@ -280,6 +278,14 @@ executables += [ 'sources' : files('test-compress.c'), 'link_with' : [libshared], }, + test_template + { + 'sources' : files('test-condition.c'), + 'dependencies' : [ + libapparmor_cflags, + libaudit_cflags, + libselinux_cflags, + ], + }, test_template + { 'sources' : files('test-coredump-stacktrace.c'), 'type' : 'manual', @@ -291,6 +297,10 @@ executables += [ 'override_options' : ['b_sanitize=none', 'strip=false', 'debug=true'], 'c_args' : ['-fno-sanitize=all', '-fno-optimize-sibling-calls', '-O1'], }, + test_template + { + 'sources' : files('test-creds.c'), + 'dependencies' : tpm2_cflags, + }, test_template + { 'sources' : files('test-crypto-util.c'), 'dependencies' : libopenssl_cflags, @@ -306,15 +316,29 @@ executables += [ 'gcrypt-util.c', ), 'dependencies' : [ + libacl_cflags, + libapparmor_cflags, + libarchive_cflags, + libaudit_cflags, libblkid_cflags, + libbpf_cflags, + libcryptsetup_cflags, + libcurl_cflags, libfdisk_cflags, + libfido2_cflags, libgcrypt_cflags, libgnutls_cflags, + libidn2_cflags, libkmod_cflags, libmicrohttpd_cflags, libmount_cflags, + libopenssl_cflags, libp11kit_cflags, + libpam_cflags, + libpcre2_cflags, libseccomp_cflags, + libselinux_cflags, + tpm2_cflags, ], }, test_template + { @@ -373,6 +397,7 @@ executables += [ test_template + { 'sources' : files('test-curl-util.c'), 'conditions' : ['HAVE_LIBCURL'], + 'dependencies' : libcurl_cflags, }, test_template + { 'sources' : files('test-libcrypt-util.c'), @@ -423,6 +448,10 @@ executables += [ 'objects' : ['test-nss-hosts'], 'conditions' : ['ENABLE_NSS'], }, + test_template + { + 'sources' : files('test-pe-binary.c'), + 'dependencies' : libopenssl_cflags, + }, test_template + { 'sources' : files('test-pkcs7-util.c'), 'dependencies' : libopenssl_cflags, @@ -476,7 +505,10 @@ executables += [ }, test_template + { 'sources' : files('test-tpm2.c'), - 'dependencies' : libopenssl_cflags, + 'dependencies' : [ + libopenssl_cflags, + tpm2_cflags, + ], 'timeout' : 120, }, test_template + { @@ -528,11 +560,17 @@ executables += [ }, core_test_template + { 'sources' : files('test-bpf-restrict-fs.c'), - 'dependencies' : common_test_dependencies, + 'dependencies' : [ + common_test_dependencies, + libbpf_cflags, + ], }, core_test_template + { 'sources' : files('test-bpf-restrict-fsaccess.c'), - 'dependencies' : common_test_dependencies, + 'dependencies' : [ + common_test_dependencies, + libbpf_cflags, + ], 'bpf_programs' : ['restrict-fsaccess'], 'type' : 'manual', }, @@ -584,7 +622,10 @@ executables += [ }, core_test_template + { 'sources' : files('test-load-fragment.c'), - 'dependencies' : common_test_dependencies, + 'dependencies' : [ + common_test_dependencies, + libpcre2_cflags, + ], }, core_test_template + { 'sources' : files('test-loop-util.c'), diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c index 6c901b1893c57..0d5171856116d 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -7,6 +7,11 @@ #include "calendarspec.h" #include "env-util.h" #include "errno-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "log.h" +#include "memfd-util.h" +#include "process-util.h" #include "string-util.h" #include "tests.h" #include "time-util.h" @@ -256,6 +261,76 @@ TEST(calendar_spec_from_string) { assert_se(calendar_spec_from_string("*:4,30:*\n", &c) == -EINVAL); } +/* Fork a child with stderr redirected to a memfd, invoke calendar_spec_from_string_full() + * with warn=true, wait, then return the memfd rewound to 0. */ +static int run_in_child_capture_stderr(const char *spec) { + _cleanup_close_ int memfd = memfd_new("calendarspec-warn-test"); + assert_se(memfd >= 0); + + int r = pidref_safe_fork_full( + "(calendarspec-warn)", + (const int[3]) { -1, -1, memfd }, + NULL, 0, + FORK_WAIT|FORK_LOG|FORK_REARRANGE_STDIO, + NULL); + assert_se(r >= 0); + + if (r == 0) { + /* log.c may have inherited a console fd that no longer points at stderr. */ + log_close(); + log_set_target_and_open(LOG_TARGET_CONSOLE); + log_set_max_level(LOG_WARNING); + + _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL; + (void) calendar_spec_from_string_full(spec, &c, /* warn_on_weekday_mismatch= */ true); + /* Write PARSE_OK to stderr as a positive anchor. */ + fputs("PARSE_OK\n", stderr); + _exit(0); + } + + assert_se(lseek(memfd, 0, SEEK_SET) == 0); + return TAKE_FD(memfd); +} + +TEST(calendar_spec_weekday_conflict) { + char buf[4096]; + ssize_t n; + + /* Case 1: conflicting weekday — warning must be emitted */ + { + _cleanup_close_ int fd = run_in_child_capture_stderr("Thu 2027-01-01"); + n = loop_read(fd, buf, sizeof(buf) - 1, /* do_poll= */ false); + assert_se(n > 0); + buf[n] = '\0'; + /* Check date, weekday text and warning phrase are present. */ + assert_se(strstr(buf, "2027-01-01")); + assert_se(strstr(buf, "which is a Fri")); + assert_se(strstr(buf, "will never elapse")); + } + + /* Case 2: correct weekday — no warning */ + { + _cleanup_close_ int fd = run_in_child_capture_stderr("Fri 2027-01-01"); + n = loop_read(fd, buf, sizeof(buf) - 1, /* do_poll= */ false); + /* Ensure child wrote PARSE_OK. */ + assert_se(n >= 0); + buf[MAX(n, (ssize_t)0)] = '\0'; + assert_se(strstr(buf, "PARSE_OK")); + assert_se(!strstr(buf, "will never elapse")); + } + + /* Case 3: wildcard date — no static conflict, no warning */ + { + _cleanup_close_ int fd = run_in_child_capture_stderr("Thu *-*-*"); + n = loop_read(fd, buf, sizeof(buf) - 1, /* do_poll= */ false); + /* Ensure child wrote PARSE_OK. */ + assert_se(n >= 0); + buf[MAX(n, (ssize_t)0)] = '\0'; + assert_se(strstr(buf, "PARSE_OK")); + assert_se(!strstr(buf, "will never elapse")); + } +} + static int intro(void) { /* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */ ASSERT_OK_ERRNO(unsetenv("TZ")); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index 4b14bd46960d1..38f6573ea1209 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -27,45 +27,45 @@ assert_cc(SUN_PATH_LEN == 108); TEST(ifname_valid) { - assert_se( ifname_valid("foo")); - assert_se( ifname_valid("eth0")); - - assert_se(!ifname_valid("0")); - assert_se(!ifname_valid("99")); - assert_se( ifname_valid("a99")); - assert_se( ifname_valid("99a")); - - assert_se(!ifname_valid(NULL)); - assert_se(!ifname_valid("")); - assert_se(!ifname_valid(" ")); - assert_se(!ifname_valid(" foo")); - assert_se(!ifname_valid("bar\n")); - assert_se(!ifname_valid(".")); - assert_se(!ifname_valid("..")); - assert_se(ifname_valid("foo.bar")); - assert_se(!ifname_valid("x:y")); - - assert_se( ifname_valid_full("xxxxxxxxxxxxxxx", 0)); - assert_se(!ifname_valid_full("xxxxxxxxxxxxxxxx", 0)); - assert_se( ifname_valid_full("xxxxxxxxxxxxxxxx", IFNAME_VALID_ALTERNATIVE)); - assert_se( ifname_valid_full("xxxxxxxxxxxxxxxx", IFNAME_VALID_ALTERNATIVE)); - assert_se(!ifname_valid_full("999", IFNAME_VALID_ALTERNATIVE)); - assert_se( ifname_valid_full("999", IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC)); - assert_se(!ifname_valid_full("0", IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC)); + ASSERT_TRUE(ifname_valid("foo")); + ASSERT_TRUE(ifname_valid("eth0")); + + ASSERT_FALSE(ifname_valid("0")); + ASSERT_FALSE(ifname_valid("99")); + ASSERT_TRUE(ifname_valid("a99")); + ASSERT_TRUE(ifname_valid("99a")); + + ASSERT_FALSE(ifname_valid(NULL)); + ASSERT_FALSE(ifname_valid("")); + ASSERT_FALSE(ifname_valid(" ")); + ASSERT_FALSE(ifname_valid(" foo")); + ASSERT_FALSE(ifname_valid("bar\n")); + ASSERT_FALSE(ifname_valid(".")); + ASSERT_FALSE(ifname_valid("..")); + ASSERT_TRUE(ifname_valid("foo.bar")); + ASSERT_FALSE(ifname_valid("x:y")); + + ASSERT_TRUE(ifname_valid_full("xxxxxxxxxxxxxxx", 0)); + ASSERT_FALSE(ifname_valid_full("xxxxxxxxxxxxxxxx", 0)); + ASSERT_TRUE(ifname_valid_full("xxxxxxxxxxxxxxxx", IFNAME_VALID_ALTERNATIVE)); + ASSERT_TRUE(ifname_valid_full("xxxxxxxxxxxxxxxx", IFNAME_VALID_ALTERNATIVE)); + ASSERT_FALSE(ifname_valid_full("999", IFNAME_VALID_ALTERNATIVE)); + ASSERT_TRUE(ifname_valid_full("999", IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC)); + ASSERT_FALSE(ifname_valid_full("0", IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC)); } static void test_socket_print_unix_one(const char *in, size_t len_in, const char *expected) { _cleanup_free_ char *out = NULL, *c = NULL; - assert_se(len_in <= SUN_PATH_LEN); + ASSERT_LE(len_in, SUN_PATH_LEN); SocketAddress a = { .sockaddr = { .un = { .sun_family = AF_UNIX } }, .size = offsetof(struct sockaddr_un, sun_path) + len_in, .type = SOCK_STREAM, }; memcpy(a.sockaddr.un.sun_path, in, len_in); - assert_se(socket_address_print(&a, &out) >= 0); - assert_se(c = cescape(in)); + ASSERT_OK(socket_address_print(&a, &out)); + ASSERT_NOT_NULL(c = cescape(in)); log_info("\"%s\" → \"%s\" (expect \"%s\")", in, out, expected); ASSERT_STREQ(out, expected); } @@ -112,13 +112,13 @@ TEST(sockaddr_equal) { .vm.svm_cid = VMADDR_CID_ANY, }; - assert_se(sockaddr_equal(&a, &a)); - assert_se(sockaddr_equal(&a, &b)); - assert_se(sockaddr_equal(&d, &d)); - assert_se(sockaddr_equal(&e, &e)); - assert_se(!sockaddr_equal(&a, &c)); - assert_se(!sockaddr_equal(&b, &c)); - assert_se(!sockaddr_equal(&a, &e)); + ASSERT_TRUE(sockaddr_equal(&a, &a)); + ASSERT_TRUE(sockaddr_equal(&a, &b)); + ASSERT_TRUE(sockaddr_equal(&d, &d)); + ASSERT_TRUE(sockaddr_equal(&e, &e)); + ASSERT_FALSE(sockaddr_equal(&a, &c)); + ASSERT_FALSE(sockaddr_equal(&b, &c)); + ASSERT_FALSE(sockaddr_equal(&a, &e)); } TEST(sockaddr_un_len) { @@ -132,25 +132,25 @@ TEST(sockaddr_un_len) { .sun_path = "\0foobar", }; - assert_se(sockaddr_un_len(&fs) == offsetof(struct sockaddr_un, sun_path) + strlen(fs.sun_path) + 1); - assert_se(sockaddr_un_len(&abstract) == offsetof(struct sockaddr_un, sun_path) + 1 + strlen(abstract.sun_path + 1)); + ASSERT_EQ(sockaddr_un_len(&fs), offsetof(struct sockaddr_un, sun_path) + strlen(fs.sun_path) + 1); + ASSERT_EQ(sockaddr_un_len(&abstract), offsetof(struct sockaddr_un, sun_path) + 1 + strlen(abstract.sun_path + 1)); } TEST(in_addr_is_multicast) { union in_addr_union a, b; int f; - assert_se(in_addr_from_string_auto("192.168.3.11", &f, &a) >= 0); - assert_se(in_addr_is_multicast(f, &a) == 0); + ASSERT_OK(in_addr_from_string_auto("192.168.3.11", &f, &a)); + ASSERT_OK_EQ(in_addr_is_multicast(f, &a), 0); - assert_se(in_addr_from_string_auto("224.0.0.1", &f, &a) >= 0); - assert_se(in_addr_is_multicast(f, &a) == 1); + ASSERT_OK(in_addr_from_string_auto("224.0.0.1", &f, &a)); + ASSERT_OK_EQ(in_addr_is_multicast(f, &a), 1); - assert_se(in_addr_from_string_auto("FF01:0:0:0:0:0:0:1", &f, &b) >= 0); - assert_se(in_addr_is_multicast(f, &b) == 1); + ASSERT_OK(in_addr_from_string_auto("FF01:0:0:0:0:0:0:1", &f, &b)); + ASSERT_OK_EQ(in_addr_is_multicast(f, &b), 1); - assert_se(in_addr_from_string_auto("2001:db8::c:69b:aeff:fe53:743e", &f, &b) >= 0); - assert_se(in_addr_is_multicast(f, &b) == 0); + ASSERT_OK(in_addr_from_string_auto("2001:db8::c:69b:aeff:fe53:743e", &f, &b)); + ASSERT_OK_EQ(in_addr_is_multicast(f, &b), 0); } TEST(getpeercred_getpeergroups) { @@ -173,38 +173,35 @@ TEST(getpeercred_getpeergroups) { test_gids = (gid_t*) gids; n_test_gids = ELEMENTSOF(gids); - assert_se(fully_set_uid_gid(test_uid, test_gid, test_gids, n_test_gids) >= 0); + ASSERT_OK(fully_set_uid_gid(test_uid, test_gid, test_gids, n_test_gids)); } else { test_uid = getuid(); test_gid = getgid(); - int ngroups_max = sysconf_ngroups_max(); - assert_se(ngroups_max > 0); + int ngroups_max = ASSERT_OK_POSITIVE(sysconf_ngroups_max()); test_gids = newa(gid_t, ngroups_max); - r = getgroups(ngroups_max, test_gids); - assert_se(r >= 0); + r = ASSERT_OK_ERRNO(getgroups(ngroups_max, test_gids)); n_test_gids = (size_t) r; } - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM, 0, pair)); - assert_se(getpeercred(pair[0], &ucred) >= 0); + ASSERT_OK(getpeercred(pair[0], &ucred)); - assert_se(ucred.uid == test_uid); - assert_se(ucred.gid == test_gid); - assert_se(ucred.pid == getpid_cached()); + ASSERT_EQ(ucred.uid, test_uid); + ASSERT_EQ(ucred.gid, test_gid); + ASSERT_EQ(ucred.pid, getpid_cached()); { _cleanup_free_ gid_t *peer_groups = NULL; - r = getpeergroups(pair[0], &peer_groups); - assert_se(r >= 0 || IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT)); + r = ASSERT_OK_OR(getpeergroups(pair[0], &peer_groups), -EOPNOTSUPP, -ENOPROTOOPT); if (r >= 0) { - assert_se((size_t) r == n_test_gids); - assert_se(memcmp(peer_groups, test_gids, sizeof(gid_t) * n_test_gids) == 0); + ASSERT_EQ((size_t) r, n_test_gids); + ASSERT_EQ(memcmp(peer_groups, test_gids, sizeof(gid_t) * n_test_gids), 0); } } @@ -218,7 +215,7 @@ TEST(passfd_read) { _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; - assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)); r = ASSERT_OK(pidref_safe_fork("(passfd_read)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL)); @@ -227,13 +224,12 @@ TEST(passfd_read) { pair[0] = safe_close(pair[0]); char tmpfile[] = "/tmp/test-socket-util-passfd-read-XXXXXX"; - assert_se(write_tmpfile(tmpfile, file_contents) == 0); + ASSERT_OK_ZERO(write_tmpfile(tmpfile, file_contents)); - _cleanup_close_ int tmpfd = open(tmpfile, O_RDONLY); - assert_se(tmpfd >= 0); - assert_se(unlink(tmpfile) == 0); + _cleanup_close_ int tmpfd = ASSERT_OK_ERRNO(open(tmpfile, O_RDONLY)); + ASSERT_OK_ERRNO(unlink(tmpfile)); - assert_se(send_one_fd(pair[1], tmpfd, MSG_DONTWAIT) == 0); + ASSERT_OK_ZERO(send_one_fd(pair[1], tmpfd, MSG_DONTWAIT)); _exit(EXIT_SUCCESS); } @@ -244,11 +240,10 @@ TEST(passfd_read) { pair[1] = safe_close(pair[1]); - assert_se(receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd) == 0); + ASSERT_OK_ZERO(receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd)); - assert_se(fd >= 0); - ssize_t n = read(fd, buf, sizeof(buf)-1); - assert_se(n >= 0); + ASSERT_OK(fd); + ssize_t n = ASSERT_OK_ERRNO(read(fd, buf, sizeof(buf)-1)); buf[n] = 0; ASSERT_STREQ(buf, file_contents); } @@ -259,7 +254,7 @@ TEST(passfd_contents_read) { static const char wire_contents[] = "test contents on the wire"; int r; - assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)); r = ASSERT_OK(pidref_safe_fork("(passfd_contents_read)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL)); @@ -270,13 +265,12 @@ TEST(passfd_contents_read) { pair[0] = safe_close(pair[0]); - assert_se(write_tmpfile(tmpfile, file_contents) == 0); + ASSERT_OK_ZERO(write_tmpfile(tmpfile, file_contents)); - _cleanup_close_ int tmpfd = open(tmpfile, O_RDONLY); - assert_se(tmpfd >= 0); - assert_se(unlink(tmpfile) == 0); + _cleanup_close_ int tmpfd = ASSERT_OK_ERRNO(open(tmpfile, O_RDONLY)); + ASSERT_OK_ERRNO(unlink(tmpfile)); - assert_se(send_one_fd_iov(pair[1], tmpfd, &iov, 1, MSG_DONTWAIT) > 0); + ASSERT_OK_POSITIVE(send_one_fd_iov(pair[1], tmpfd, &iov, 1, MSG_DONTWAIT)); _exit(EXIT_SUCCESS); } @@ -288,14 +282,12 @@ TEST(passfd_contents_read) { pair[1] = safe_close(pair[1]); - k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); - assert_se(k > 0); + k = ASSERT_OK_POSITIVE(receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd)); buf[k] = 0; ASSERT_STREQ(buf, wire_contents); - assert_se(fd >= 0); - r = read(fd, buf, sizeof(buf)-1); - assert_se(r >= 0); + ASSERT_OK(fd); + r = ASSERT_OK_ERRNO(read(fd, buf, sizeof(buf)-1)); buf[r] = 0; ASSERT_STREQ(buf, file_contents); } @@ -305,10 +297,9 @@ TEST(receive_nopassfd) { static const char wire_contents[] = "no fd passed here"; int r; - assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)); r = ASSERT_OK(pidref_safe_fork("(receive_nopassfd)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL)); - assert_se(r >= 0); if (r == 0) { /* Child */ @@ -316,7 +307,7 @@ TEST(receive_nopassfd) { pair[0] = safe_close(pair[0]); - assert_se(send_one_fd_iov(pair[1], -1, &iov, 1, MSG_DONTWAIT) > 0); + ASSERT_OK_POSITIVE(send_one_fd_iov(pair[1], -1, &iov, 1, MSG_DONTWAIT)); _exit(EXIT_SUCCESS); } @@ -328,20 +319,19 @@ TEST(receive_nopassfd) { pair[1] = safe_close(pair[1]); - k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); - assert_se(k > 0); + k = ASSERT_OK_POSITIVE(receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd)); buf[k] = 0; ASSERT_STREQ(buf, wire_contents); /* no fd passed here, confirm it was reset */ - assert_se(fd == -EBADF); + ASSERT_EQ(fd, -EBADF); } TEST(send_nodata_nofd) { _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; - assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)); r = ASSERT_OK(pidref_safe_fork("(send_nodata_nofd)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL)); @@ -349,7 +339,7 @@ TEST(send_nodata_nofd) { /* Child */ pair[0] = safe_close(pair[0]); - assert_se(send_one_fd_iov(pair[1], -1, NULL, 0, MSG_DONTWAIT) == -EINVAL); + ASSERT_ERROR(send_one_fd_iov(pair[1], -1, NULL, 0, MSG_DONTWAIT), EINVAL); _exit(EXIT_SUCCESS); } @@ -357,23 +347,21 @@ TEST(send_nodata_nofd) { char buf[64]; struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); int fd = -999; - ssize_t k; pair[1] = safe_close(pair[1]); - k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); /* recvmsg() will return errno EAGAIN if nothing was sent */ - assert_se(k == -EAGAIN); + ASSERT_ERROR(receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd), EAGAIN); /* receive_one_fd_iov returned error, so confirm &fd wasn't touched */ - assert_se(fd == -999); + ASSERT_EQ(fd, -999); } TEST(send_emptydata) { _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; - assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair)); r = ASSERT_OK(pidref_safe_fork("(send_emptydata)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL)); @@ -382,7 +370,7 @@ TEST(send_emptydata) { pair[0] = safe_close(pair[0]); /* This will succeed, since iov is set. */ - assert_se(send_one_fd_iov(pair[1], -1, &iovec_empty, 1, MSG_DONTWAIT) == 0); + ASSERT_OK_ZERO(send_one_fd_iov(pair[1], -1, &iovec_empty, 1, MSG_DONTWAIT)); _exit(EXIT_SUCCESS); } @@ -390,16 +378,14 @@ TEST(send_emptydata) { char buf[64]; struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); int fd = -999; - ssize_t k; pair[1] = safe_close(pair[1]); - k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); /* receive_one_fd_iov() returns -EIO if an fd is not found and no data was returned. */ - assert_se(k == -EIO); + ASSERT_ERROR(receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd), EIO); /* receive_one_fd_iov returned error, so confirm &fd wasn't touched */ - assert_se(fd == -999); + ASSERT_EQ(fd, -999); } TEST(flush_accept) { @@ -408,59 +394,49 @@ TEST(flush_accept) { union sockaddr_union lsa; socklen_t l; - listen_stream = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - assert_se(listen_stream >= 0); - - listen_dgram = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - assert_se(listen_dgram >= 0); - - listen_seqpacket = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - assert_se(listen_seqpacket >= 0); - - assert_se(flush_accept(listen_stream) < 0); - assert_se(flush_accept(listen_dgram) < 0); - assert_se(flush_accept(listen_seqpacket) < 0); - - assert_se(bind(listen_stream, &sa.sa, sizeof(sa_family_t)) >= 0); - assert_se(bind(listen_dgram, &sa.sa, sizeof(sa_family_t)) >= 0); - assert_se(bind(listen_seqpacket, &sa.sa, sizeof(sa_family_t)) >= 0); + ASSERT_OK_ERRNO(listen_stream = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)); + ASSERT_OK_ERRNO(listen_dgram = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)); + ASSERT_OK_ERRNO(listen_seqpacket = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)); - assert_se(flush_accept(listen_stream) < 0); - assert_se(flush_accept(listen_dgram) < 0); - assert_se(flush_accept(listen_seqpacket) < 0); + ASSERT_FAIL(flush_accept(listen_stream)); + ASSERT_FAIL(flush_accept(listen_dgram)); + ASSERT_FAIL(flush_accept(listen_seqpacket)); - assert_se(listen(listen_stream, SOMAXCONN_DELUXE) >= 0); - assert_se(listen(listen_dgram, SOMAXCONN_DELUXE) < 0); - assert_se(listen(listen_seqpacket, SOMAXCONN_DELUXE) >= 0); + ASSERT_OK_ERRNO(bind(listen_stream, &sa.sa, sizeof(sa_family_t))); + ASSERT_OK_ERRNO(bind(listen_dgram, &sa.sa, sizeof(sa_family_t))); + ASSERT_OK_ERRNO(bind(listen_seqpacket, &sa.sa, sizeof(sa_family_t))); - assert_se(flush_accept(listen_stream) >= 0); - assert_se(flush_accept(listen_dgram) < 0); - assert_se(flush_accept(listen_seqpacket) >= 0); + ASSERT_FAIL(flush_accept(listen_stream)); + ASSERT_FAIL(flush_accept(listen_dgram)); + ASSERT_FAIL(flush_accept(listen_seqpacket)); - connect_stream = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - assert_se(connect_stream >= 0); + ASSERT_OK_ERRNO(listen(listen_stream, SOMAXCONN_DELUXE)); + ASSERT_FAIL(listen(listen_dgram, SOMAXCONN_DELUXE)); + ASSERT_OK_ERRNO(listen(listen_seqpacket, SOMAXCONN_DELUXE)); - connect_dgram = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - assert_se(connect_dgram >= 0); + ASSERT_OK(flush_accept(listen_stream)); + ASSERT_FAIL(flush_accept(listen_dgram)); + ASSERT_OK(flush_accept(listen_seqpacket)); - connect_seqpacket = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - assert_se(connect_seqpacket >= 0); + ASSERT_OK_ERRNO(connect_stream = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)); + ASSERT_OK_ERRNO(connect_dgram = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)); + ASSERT_OK_ERRNO(connect_seqpacket = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)); l = sizeof(lsa); - assert_se(getsockname(listen_stream, &lsa.sa, &l) >= 0); - assert_se(connect(connect_stream, &lsa.sa, l) >= 0); + ASSERT_OK_ERRNO(getsockname(listen_stream, &lsa.sa, &l)); + ASSERT_OK_ERRNO(connect(connect_stream, &lsa.sa, l)); l = sizeof(lsa); - assert_se(getsockname(listen_dgram, &lsa.sa, &l) >= 0); - assert_se(connect(connect_dgram, &lsa.sa, l) >= 0); + ASSERT_OK_ERRNO(getsockname(listen_dgram, &lsa.sa, &l)); + ASSERT_OK_ERRNO(connect(connect_dgram, &lsa.sa, l)); l = sizeof(lsa); - assert_se(getsockname(listen_seqpacket, &lsa.sa, &l) >= 0); - assert_se(connect(connect_seqpacket, &lsa.sa, l) >= 0); + ASSERT_OK_ERRNO(getsockname(listen_seqpacket, &lsa.sa, &l)); + ASSERT_OK_ERRNO(connect(connect_seqpacket, &lsa.sa, l)); - assert_se(flush_accept(listen_stream) >= 0); - assert_se(flush_accept(listen_dgram) < 0); - assert_se(flush_accept(listen_seqpacket) >= 0); + ASSERT_OK(flush_accept(listen_stream)); + ASSERT_FAIL(flush_accept(listen_dgram)); + ASSERT_OK(flush_accept(listen_seqpacket)); } TEST(ipv6_enabled) { @@ -475,39 +451,35 @@ TEST(sockaddr_un_set_path) { union sockaddr_union sa; _cleanup_close_ int fd1, fd2, fd3; - assert_se(mkdtemp_malloc("/tmp/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXXXX", &t) >= 0); - assert_se(strlen(t) > SUN_PATH_LEN); + ASSERT_OK(mkdtemp_malloc("/tmp/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaXXXXXX", &t)); + ASSERT_GT(strlen(t), SUN_PATH_LEN); - assert_se(j = path_join(t, "sock")); - assert_se(sockaddr_un_set_path(&sa.un, j) == -ENAMETOOLONG); /* too long for AF_UNIX socket */ + ASSERT_NOT_NULL(j = path_join(t, "sock")); + ASSERT_ERROR(sockaddr_un_set_path(&sa.un, j), ENAMETOOLONG); /* too long for AF_UNIX socket */ - assert_se(asprintf(&sh, "/tmp/%" PRIx64, random_u64()) >= 0); - assert_se(symlink(t, sh) >= 0); /* create temporary symlink, to access it anyway */ + ASSERT_OK_ERRNO(asprintf(&sh, "/tmp/%" PRIx64, random_u64())); + ASSERT_OK_ERRNO(symlink(t, sh)); /* create temporary symlink, to access it anyway */ free(j); - assert_se(j = path_join(sh, "sock")); - assert_se(sockaddr_un_set_path(&sa.un, j) >= 0); + ASSERT_NOT_NULL(j = path_join(sh, "sock")); + ASSERT_OK(sockaddr_un_set_path(&sa.un, j)); - fd1 = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - assert_se(fd1 >= 0); - assert_se(bind(fd1, &sa.sa, sockaddr_len(&sa)) >= 0); - assert_se(listen(fd1, 1) >= 0); + ASSERT_OK_ERRNO(fd1 = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0)); + ASSERT_OK_ERRNO(bind(fd1, &sa.sa, sockaddr_len(&sa))); + ASSERT_OK_ERRNO(listen(fd1, 1)); sh = unlink_and_free(sh); /* remove temporary symlink */ - fd2 = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - assert_se(fd2 >= 0); - assert_se(connect(fd2, &sa.sa, sockaddr_len(&sa)) < 0); - assert_se(errno == ENOENT); /* we removed the symlink, must fail */ + ASSERT_OK_ERRNO(fd2 = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0)); + ASSERT_ERROR_ERRNO(connect(fd2, &sa.sa, sockaddr_len(&sa)), ENOENT); /* we removed the symlink, must fail */ free(j); - assert_se(j = path_join(t, "sock")); + ASSERT_NOT_NULL(j = path_join(t, "sock")); - fd3 = open(j, O_CLOEXEC|O_PATH|O_NOFOLLOW); - assert_se(fd3 > 0); - assert_se(sockaddr_un_set_path(&sa.un, FORMAT_PROC_FD_PATH(fd3)) >= 0); /* connect via O_PATH instead, circumventing 108ch limit */ + ASSERT_OK_ERRNO(fd3 = open(j, O_CLOEXEC|O_PATH|O_NOFOLLOW)); + ASSERT_OK(sockaddr_un_set_path(&sa.un, FORMAT_PROC_FD_PATH(fd3))); /* connect via O_PATH instead, circumventing 108ch limit */ - assert_se(connect(fd2, &sa.sa, sockaddr_len(&sa)) >= 0); + ASSERT_OK_ERRNO(connect(fd2, &sa.sa, sockaddr_len(&sa))); } TEST(getpeerpidref) { diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index 392b06214b0ff..1a1370eda6da7 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -433,15 +433,27 @@ static void test_format_timestamp_impl(usec_t x) { if (x_sec == y_sec && streq(xx, yy)) return; /* Yay! */ - /* When the timezone is built with rearguard being enabled (e.g. old Ubuntu and RHEL), the timezone - * Africa/Windhoek may provide time shifted 1 hour from the original. See - * https://github.com/systemd/systemd/issues/28472 and https://github.com/systemd/systemd/pull/35471. - * Also, the same may happen on MSK timezone (e.g. Europe/Volgograd or Europe/Kirov), or on - * Africa/Tripoli (Libya) which switched between CET and EET multiple times historically, causing - * certain timestamps to round-trip with a 1h offset. */ + /* There are two classes of known round-trip failures that produce exactly a 1h offset: + * + * 1) The formatted abbreviation doesn't match the current timezone's abbreviations - the timestamp + * is from a historical era (e.g. Africa/Tripoli switched between CET and EET multiple times + * historically, America/Cancun switched between EST/EDT and CST/CDT several times in the past, + * etc.), and the round-trip is inherently unreliable on platforms where parse_gmtoff() resolves + * such abbreviations with incorrect offsets. + * + * 2) Rearguard/vanguard database format differences where the abbreviation matches but the + * offset is still wrong (e.g. Africa/Windhoek, Europe/Kirov, Europe/Volgograd). + * + * See: + * - https://github.com/systemd/systemd/issues/28472 + * - https://github.com/systemd/systemd/pull/35471 + * - https://github.com/systemd/systemd/issues/37684 + */ bool ignore = - (STRPTR_IN_SET(getenv("TZ"), "Africa/Windhoek", "Africa/Tripoli", "Libya") || - STRPTR_IN_SET(get_tzname(/* dst= */ false), "CAT", "EAT", "MSK", "WET")) && + ((!streq_ptr(tz, get_tzname(/* dst= */ false)) && + !streq_ptr(tz, get_tzname(/* dst= */ true))) || + streq_ptr(getenv("TZ"), "Africa/Windhoek") || + STRPTR_IN_SET(get_tzname(/* dst= */ false), "MSK", "WET")) && (x_sec > y_sec ? x_sec - y_sec : y_sec - x_sec) == 3600; log_full(ignore ? LOG_WARNING : LOG_ERR, @@ -459,16 +471,17 @@ static void test_format_timestamp_loop(void) { test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX-1); test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX); - /* Two cases which trigger https://github.com/systemd/systemd/issues/28472 */ + /* Specific timestamps known to cause a 1h round-trip discrepancy with certain timezones: + * + * Two cases which trigger https://github.com/systemd/systemd/issues/28472. */ test_format_timestamp_impl(1504938962980066); test_format_timestamp_impl(1509482094632752); - /* With tzdata-2025c, the timestamp (randomly?) fails on MSK time zone (e.g. Europe/Volgograd). */ test_format_timestamp_impl(1414277092997572); - - /* Africa/Tripoli (Libya) switched from CET to EET multiple times in the past, causing a 1h - * round-trip discrepancy for historical timestamps. */ + /* Africa/Tripoli (Libya) switched from CET to EET multiple times in the past. */ test_format_timestamp_impl(378687574661411); + /* America/Cancun switched from EST/EDT to CST/CDT multiple times in the past. */ + test_format_timestamp_impl(902035565603993); for (unsigned i = 0; i < TRIAL; i++) { usec_t x; diff --git a/src/tmpfiles/meson.build b/src/tmpfiles/meson.build index 2a7728c295533..fcf2749e4bf2b 100644 --- a/src/tmpfiles/meson.build +++ b/src/tmpfiles/meson.build @@ -12,7 +12,10 @@ executables += [ 'public' : true, 'extract' : files('tmpfiles.c') + offline_passwd_c, - 'dependencies' : libacl_cflags, + 'dependencies' : [ + libacl_cflags, + libselinux_cflags, + ], }, executable_template + { 'name' : 'systemd-tmpfiles.standalone', @@ -24,7 +27,10 @@ executables += [ libshared_static, libsystemd_static, ], - 'dependencies' : libacl_cflags, + 'dependencies' : [ + libacl_cflags, + libselinux_cflags, + ], }, test_template + { 'sources' : files('test-offline-passwd.c') + diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build index 55f0ce26d31cb..bb3c3338ba251 100644 --- a/src/tpm2-setup/meson.build +++ b/src/tpm2-setup/meson.build @@ -11,6 +11,7 @@ executables += [ ], 'dependencies' : [ libopenssl_cflags, + tpm2_cflags, ], }, libexec_template + { @@ -21,6 +22,9 @@ executables += [ 'HAVE_OPENSSL', 'HAVE_TPM2', ], + 'dependencies' : [ + tpm2_cflags, + ], }, libexec_template + { 'name' : 'systemd-tpm2-swtpm', @@ -39,6 +43,9 @@ executables += [ 'HAVE_OPENSSL', 'HAVE_TPM2', ], + 'dependencies' : [ + tpm2_cflags, + ], }, ] diff --git a/src/tpm2-setup/nvpcr/hardware.nvpcr.in b/src/tpm2-setup/nvpcr/hardware.nvpcr.in index bb4a3afa6957a..0fdf32d309459 100644 --- a/src/tpm2-setup/nvpcr/hardware.nvpcr.in +++ b/src/tpm2-setup/nvpcr/hardware.nvpcr.in @@ -2,5 +2,6 @@ "name" : "hardware", "algorithm" : "sha256", "nvIndex" : {{TPM2_NVPCR_BASE + 0}}, - "priority" : 500 + "priority" : 500, + "orderly" : false } diff --git a/src/udev/meson.build b/src/udev/meson.build index 375645e7593ed..6c2232c74c6dc 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -122,6 +122,8 @@ udev_dependencies = [ libblkid_cflags, libkmod_cflags, libmount_cflags, + libselinux_cflags, + tpm2_cflags, ] udev_plugin_template = executable_template + { diff --git a/src/veritysetup/meson.build b/src/veritysetup/meson.build index 21432d41f1658..437e58ecd834e 100644 --- a/src/veritysetup/meson.build +++ b/src/veritysetup/meson.build @@ -8,7 +8,10 @@ executables += [ libexec_template + { 'name' : 'systemd-veritysetup', 'sources' : files('veritysetup.c'), - 'dependencies' : libcryptsetup_cflags, + 'dependencies' : [ + libcryptsetup_cflags, + tpm2_cflags, + ], }, generator_template + { 'name' : 'systemd-veritysetup-generator', diff --git "a/test/fuzz/fuzz-unit-file/dm-back\\x2dslash.swap" b/test/fuzz/fuzz-unit-file/dm-backslash.swap similarity index 100% rename from "test/fuzz/fuzz-unit-file/dm-back\\x2dslash.swap" rename to test/fuzz/fuzz-unit-file/dm-backslash.swap diff --git a/test/integration-tests/TEST-93-CLONESETUP/meson.build b/test/integration-tests/TEST-93-CLONESETUP/meson.build new file mode 100644 index 0000000000000..77370ce4588c4 --- /dev/null +++ b/test/integration-tests/TEST-93-CLONESETUP/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +integration_tests += [ + integration_test_template + { + 'name' : fs.name(meson.current_source_dir()), + 'vm' : true, + }, +] diff --git a/test/integration-tests/meson.build b/test/integration-tests/meson.build index 3991b11e79cc6..228d808f51de7 100644 --- a/test/integration-tests/meson.build +++ b/test/integration-tests/meson.build @@ -105,6 +105,7 @@ foreach dirname : [ 'TEST-90-RESTRICT-FSACCESS', 'TEST-91-LIVEUPDATE', 'TEST-92-TPM2-SWTPM', + 'TEST-93-CLONESETUP', ] subdir(dirname) endforeach diff --git a/test/units/TEST-74-AUX-UTILS.report.sh b/test/units/TEST-74-AUX-UTILS.report.sh index 06914fdc50324..627e7fef6554e 100755 --- a/test/units/TEST-74-AUX-UTILS.report.sh +++ b/test/units/TEST-74-AUX-UTILS.report.sh @@ -109,6 +109,17 @@ fi [ "$(metric_value UserspaceTimestamp.Realtime)" -gt 0 ] [ "$(metric_value UserspaceTimestamp.Monotonic)" -gt 0 ] +# These startup phases are captured unconditionally by manager_startup() on every boot (in containers +# too, where they are not redirected to the InitRD* variants), so both clocks are always set on the +# system manager by the time this service runs. We don't check UnitsLoadTimestamp (the last load, only +# set on the reload/reexec triggered by switch-root) nor the Security/Firmware/Loader/InitRD timestamps, +# which depend on the boot environment and may legitimately be unset (the metric is then suppressed). +for phase in GeneratorsStartTimestamp GeneratorsFinishTimestamp \ + UnitsLoadStartTimestamp UnitsLoadFinishTimestamp; do + [ "$(metric_value "$phase.Realtime")" -gt 0 ] + [ "$(metric_value "$phase.Monotonic")" -gt 0 ] +done + # test io.systemd.Basic.MachineInfo.* metrics, sourced from /etc/machine-info if [ -e /etc/machine-info ]; then MACHINE_INFO_BACKUP="$(mktemp)" diff --git a/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh b/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh index d4ca6e0a0d163..7c51e89ebbd35 100755 --- a/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh +++ b/test/units/TEST-87-AUX-UTILS-VM.sysinstall.sh @@ -34,12 +34,15 @@ if systemd-detect-virt -cq; then exit 0 fi +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh -WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)" -LOOPDEV="" -MOUNTED=0 + +CRED_VALUE="systemd-sysinstall test credential payload" +CRED_VALUE_BASE64=$(echo -n "$CRED_VALUE" | base64 -w0) cleanup() { set +e @@ -53,42 +56,42 @@ cleanup() { fi rm -rf "$WORKDIR" } -trap cleanup EXIT -# 1) Build a small fake "OS source" tree. systemd-sysinstall picks this up via -# the repart.sysinstall.d definitions: CopyFiles= seeds the new root -# partition with these files. -SOURCE_ROOT="$WORKDIR/sourceroot" -mkdir -p "$SOURCE_ROOT/usr/lib" "$SOURCE_ROOT/etc" +create_fake_os_source_tree() { + # 1) Build a small fake "OS source" tree. systemd-sysinstall picks this up via + # the repart.sysinstall.d definitions: CopyFiles= seeds the new root + # partition with these files. + SOURCE_ROOT="$WORKDIR/sourceroot" + mkdir -p "$SOURCE_ROOT/usr/lib" "$SOURCE_ROOT/etc" -cat >"$SOURCE_ROOT/usr/lib/os-release" <<'EOF' + cat >"$SOURCE_ROOT/usr/lib/os-release" <<'EOF' ID=testos NAME="Test OS" PRETTY_NAME="Test OS for systemd-sysinstall" VERSION_ID=1 EOF -ln -s ../usr/lib/os-release "$SOURCE_ROOT/etc/os-release" - -# 2) Build a minimal UKI. bootctl link only requires a valid PE with .osrel and -# the systemd-stub SBAT marker, so the .linux/.initrd contents do not need -# to be a real kernel. -echo "fake-kernel" >"$WORKDIR/vmlinuz" -echo "fake-initrd" >"$WORKDIR/initrd" - -ukify build \ - --linux "$WORKDIR/vmlinuz" \ - --initrd "$WORKDIR/initrd" \ - --os-release "@$SOURCE_ROOT/usr/lib/os-release" \ - --uname "1.2.3-testkernel" \ - --cmdline "quiet" \ - --output "$WORKDIR/testuki.efi" - -# 3) Build a sysinstall partition definition: a single ESP plus a root -# partition seeded from the fake source tree. -DEFS="$WORKDIR/sysinstall.d" -mkdir -p "$DEFS" - -cat >"$DEFS/10-esp.conf" <"$WORKDIR/vmlinuz" + echo "fake-initrd" >"$WORKDIR/initrd" + + ukify build \ + --linux "$WORKDIR/vmlinuz" \ + --initrd "$WORKDIR/initrd" \ + --os-release "@$SOURCE_ROOT/usr/lib/os-release" \ + --uname "1.2.3-testkernel" \ + --cmdline "quiet" \ + --output "$WORKDIR/testuki.efi" + + # 3) Build a sysinstall partition definition: a single ESP plus a root + # partition seeded from the fake source tree. + DEFS="$WORKDIR/sysinstall.d" + mkdir -p "$DEFS" + + cat >"$DEFS/10-esp.conf" <"$DEFS/20-root.conf" <"$DEFS/20-root.conf" </dev/null + + # The UKI file referenced in the entry must exist on the ESP. + UKI_PATH=$(awk '/^uki / { print $2 }' "$ENTRY") + test -n "$UKI_PATH" + test -f "$ESP$UKI_PATH" + + # bootctl install should have placed sd-boot on the ESP. + find "$ESP/EFI/systemd" -type f -iname 'systemd-boot*.efi' | grep . >/dev/null + + # The credential we passed via --set-credential= must have been encrypted and + # placed next to the UKI, and must be referenced as 'extra' from the entry. + UKI_DIR="$(dirname "$ESP$UKI_PATH")" + TOKEN_DIR="$(basename "$UKI_DIR")" + test -s "$UKI_DIR/marker.cred" + grep -E "^extra /$TOKEN_DIR/marker\.cred$" "$ENTRY" >/dev/null + + # Locale/keymap/timezone propagation is off, so those .cred files must NOT + # exist on the ESP. + test ! -e "$UKI_DIR/firstboot.locale.cred" + test ! -e "$UKI_DIR/firstboot.keymap.cred" + test ! -e "$UKI_DIR/firstboot.timezone.cred" + + # 3) The seeded files from the fake source tree must end up in the new root. + test -f "$MNT/usr/lib/os-release" + grep '^ID=testos$' "$MNT/usr/lib/os-release" >/dev/null +} -# 5) Run the installer non-interactively against the target image. Also stash a -# literal credential ('marker') so we can verify it ends up next to the UKI -# and is referenced from the boot loader entry. -CRED_VALUE="systemd-sysinstall test credential payload" -systemd-sysinstall \ - --welcome=no \ - --chrome=no \ - --confirm=no \ - --summary=no \ - --erase=yes \ - --variables=no \ - --reboot=no \ - --mute-console=no \ - --copy-locale=no \ - --copy-keymap=no \ - --copy-timezone=no \ - --set-credential="marker:$CRED_VALUE" \ - --kernel="$WORKDIR/testuki.efi" \ - --definitions="$DEFS" \ - "$WORKDIR/target.img" - -# 6) Attach the freshly installed image as a loopback device for inspection. -LOOPDEV="$(systemd-dissect --attach "$WORKDIR/target.img")" - -# Verify the resulting on-disk layout. The disk must now carry a GPT with at -# least an ESP partition. -sfdisk_dump="$(sfdisk --dump "$LOOPDEV")" -assert_in "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" "$sfdisk_dump" - -# 7) Mount the image read-only and verify the installed artifacts: an entry -# file referencing the UKI on the ESP, the UKI itself, and the systemd-boot -# binary. -MNT="$WORKDIR/mnt" -mkdir -p "$MNT" - -systemd-dissect --mount --read-only "$LOOPDEV" "$MNT" -MOUNTED=1 - -ESP="$MNT/efi" -test -d "$ESP/loader/entries" - -# Exactly one entry should have been linked, and it should reference the UKI -# we passed via --kernel=. -ENTRY=$(find "$ESP/loader/entries" -maxdepth 1 -name '*.conf' -type f | head -n1) -test -n "$ENTRY" -grep -E "^uki /[^/]+/testuki\.efi$" "$ENTRY" >/dev/null - -# The UKI file referenced in the entry must exist on the ESP. -UKI_PATH=$(awk '/^uki / { print $2 }' "$ENTRY") -test -n "$UKI_PATH" -test -f "$ESP$UKI_PATH" - -# bootctl install should have placed sd-boot on the ESP. -find "$ESP/EFI/systemd" -type f -iname 'systemd-boot*.efi' | grep . >/dev/null - -# The credential we passed via --set-credential= must have been encrypted and -# placed next to the UKI, and must be referenced as 'extra' from the entry. -UKI_DIR="$(dirname "$ESP$UKI_PATH")" -TOKEN_DIR="$(basename "$UKI_DIR")" -test -s "$UKI_DIR/marker.cred" -grep -E "^extra /$TOKEN_DIR/marker\.cred$" "$ENTRY" >/dev/null - -# Locale/keymap/timezone propagation is off, so those .cred files must NOT -# exist on the ESP. -test ! -e "$UKI_DIR/firstboot.locale.cred" -test ! -e "$UKI_DIR/firstboot.keymap.cred" -test ! -e "$UKI_DIR/firstboot.timezone.cred" - -# 8) The seeded files from the fake source tree must end up in the new root. -test -f "$MNT/usr/lib/os-release" -grep '^ID=testos$' "$MNT/usr/lib/os-release" >/dev/null +testcase_sysinstall_basic() { + WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)" + LOOPDEV="" + MOUNTED=0 + + echo "WORKDIR=$WORKDIR" + + trap cleanup RETURN + + create_fake_os_source_tree + + # Run the installer non-interactively against the target image. Also stash a + # literal credential ('marker') so we can verify it ends up next to the UKI + # and is referenced from the boot loader entry. + systemd-sysinstall \ + --welcome=no \ + --chrome=no \ + --confirm=no \ + --summary=no \ + --erase=yes \ + --variables=no \ + --reboot=no \ + --mute-console=no \ + --copy-locale=no \ + --copy-keymap=no \ + --copy-timezone=no \ + --set-credential="marker:$CRED_VALUE" \ + --kernel="$WORKDIR/testuki.efi" \ + --definitions="$DEFS" \ + "$WORKDIR/target.img" + + validate_image + + cleanup +} + +testcase_sysinstall_varlink_basic() { + WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)" + LOOPDEV="" + MOUNTED=0 + + echo "WORKDIR=$WORKDIR" + + trap cleanup RETURN + + create_fake_os_source_tree + + # Run the installer via varlink against the target image. Also stash a + # literal credential ('marker') so we can verify it ends up next to the UKI + # and is referenced from the boot loader entry. + varlinkctl call /run/systemd/io.systemd.SysInstall io.systemd.SysInstall.Run "{\"erase\": true, \"variables\": false, \"credentials\" : [{ \"id\" : \"marker\", \"value\" : \"$CRED_VALUE_BASE64\" }], \"kernelImagePath\" : \"$WORKDIR/testuki.efi\", \"node\": \"$WORKDIR/target.img\", \"definitions\" : [\"$DEFS\"] }" --more + + validate_image +} + +run_testcases diff --git a/test/units/TEST-93-CLONESETUP.sh b/test/units/TEST-93-CLONESETUP.sh new file mode 100755 index 0000000000000..f80df58ca9c69 --- /dev/null +++ b/test/units/TEST-93-CLONESETUP.sh @@ -0,0 +1,344 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail +set -E + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +CLONESETUP_BIN=/usr/lib/systemd/systemd-clonesetup +# Test clonesetup generator and systemd-clonesetup +# Disable pager usage so interactive/manual runs do not block in `less`. +export SYSTEMD_PAGER=cat +export SYSTEMD_LESS= + +create_loop_triplet() { + local workdir="${1:?}" + local src_img dst_img meta_img + local loop_src loop_dst loop_meta + + src_img="$workdir/source.img" + dst_img="$workdir/dest.img" + meta_img="$workdir/meta.img" + + truncate -s 32M "$src_img" + truncate -s 32M "$dst_img" + truncate -s 8M "$meta_img" + + loop_src="$(losetup --show --find "$src_img")" + loop_dst="$(losetup --show --find "$dst_img")" + loop_meta="$(losetup --show --find "$meta_img")" + + # Wait for udev to process new loop devices before they are consumed. + udevadm settle --timeout=60 + + printf '%s %s %s\n' "$loop_src" "$loop_dst" "$loop_meta" +} + +cleanup_loop_triplet() { + local loop_src="${1:-}" + local loop_dst="${2:-}" + local loop_meta="${3:-}" + + [[ -n "$loop_src" ]] && losetup -d "$loop_src" + [[ -n "$loop_dst" ]] && losetup -d "$loop_dst" + [[ -n "$loop_meta" ]] && losetup -d "$loop_meta" +} + +at_exit() { + set +e + + rm -f /etc/clonetab + [[ -e /tmp/clonetab.bak ]] && cp -fv /tmp/clonetab.bak /etc/clonetab + dmsetup remove testclonesetup 2>/dev/null || true + + systemctl daemon-reload +} + +at_error() { + local rc="$?" + local source="${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}" + local line="${BASH_LINENO[0]:-0}" + local func="${FUNCNAME[1]:-main}" + + echo "ERROR: rc=$rc at $source:$line ($func): $BASH_COMMAND" >&2 + return "$rc" +} + +trap at_exit EXIT +trap at_error ERR + +clonesetup_start_and_check() { + local volume unit + + volume="${1:?}" + unit="systemd-clonesetup@$volume.service" + + # The unit existence check should always pass + [[ "$(systemctl show -P LoadState "$unit")" == loaded ]] + systemctl list-unit-files "$unit" + + systemctl start "$unit" + # wait for udev to create /dev/mapper/ node after DM device activation + udevadm settle --timeout=10 + systemctl status "$unit" + test -e "/dev/mapper/$volume" + dmsetup status "$volume" + + systemctl stop "$unit" + # wait for udev to finish processing so the device node state is in sync + # before the API returns. + udevadm settle --timeout=10 + test ! -e "/dev/mapper/$volume" +} + +prereq() { + # Skip when kernel lacks dm-clone (CONFIG_DM_CLONE) + modprobe dm_clone 2>/dev/null || true + if [[ ! -d /sys/module/dm_clone ]]; then + echo "no dm-clone" >/skipped + exit 77 + fi + echo "Found required kernel module: dm_clone" +} + +prereq + +# Backup existing clonetab if any +[[ -e /etc/clonetab ]] && cp -fv /etc/clonetab /tmp/clonetab.bak + +# Create test clonetab +clonetab_create() { + local loop_name="${1:?}" + local loop_src="${2:?}" + local loop_dst="${3:?}" + local loop_meta="${4:?}" + local loop_options="${5:?}" + + cat >/etc/clonetab </etc/clonetab </etc/clonetab </etc/clonetab </etc/clonetab </etc/clonetab <