diff options
212 files changed, 4886 insertions, 1695 deletions
diff --git a/.github/workflows/mkosi.yml b/.github/workflows/mkosi.yml index 3a8dabd95c..1b2f77c559 100644 --- a/.github/workflows/mkosi.yml +++ b/.github/workflows/mkosi.yml @@ -92,7 +92,7 @@ jobs: steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - - uses: systemd/mkosi@6972f9efba5c8472d990be3783b7e7dbf76e109e + - uses: systemd/mkosi@70aa901697f12182ccaa24e2325867d275479b55 # Freeing up disk space with rm -rf can take multiple minutes. Since we don't need the extra free space # immediately, we remove the files in the background. However, we first move them to a different location diff --git a/LICENSES/README.md b/LICENSES/README.md index ea21bafcac..76fd437cf7 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -13,7 +13,14 @@ The 'LICENSES/' directory contains all the licenses used by the sources included the systemd project source tree. Unless otherwise noted, the systemd project sources are licensed under the terms -and conditions of the **GNU Lesser General Public License v2.1 or later**. +and conditions of +**LGPL-2.1-or-later** (**GNU Lesser General Public License v2.1 or later**). + +Unless otherwise noted, compiled programs and all shared or static libraries +include sources under **LGPL-2.1-or-later** along with more permissive +licenses, and are effectively licensed **LGPL-2.1-or-later**. +systemd-udevd and other udev helper programs also include sources under +**GPL-2.0-or-later**, and are effectively licensed **GPL-2.0-or-later**. New sources that cannot be distributed under LGPL-2.1-or-later will no longer be accepted for inclusion in the systemd project to maintain license uniformity. @@ -22,8 +29,9 @@ be accepted for inclusion in the systemd project to maintain license uniformity. The following exceptions apply: - * some udev sources under src/udev/ are licensed under **GPL-2.0-or-later**, so the - udev binaries as a whole are also distributed under **GPL-2.0-or-later**. + * some sources under src/udev/ are licensed under **GPL-2.0-or-later**, + so all udev programs (`systemd-udevd`, `udevadm`, and the udev builtins + and test programs) are also distributed under **GPL-2.0-or-later**. * the header files contained in src/basic/linux/ and src/shared/linux/ are copied verbatim from the Linux kernel source tree and are licensed under **GPL-2.0 WITH Linux-syscall-note** and are used within the scope of the Linux-syscall-note @@ -130,6 +130,39 @@ Deprecations and removals: Features: +* tmpfiles: add "owning" flag for lines that limits effect of --purge + +* signed bpf loading: to address need for signature verification for bpf + programs when they are loaded, and given the bpf folks don't think this is + realistic in kernel space, maybe add small daemon that facilitates this + loading on request of clients, validates signatures and then loads the + programs. This daemon should be the only daemon with privs to do load BPF on + the system. It might be a good idea to run this daemon already in the initrd, + and leave it around during the initrd transition, to continue serve requests. + Should then live in its own fs namespace that inherits from the initrd's + fs tree, not from the host, to isolate it properly. Should set + PR_SET_DUMPABLE so that it cannot be ptraced from the host. Should have + CAP_SYS_BPF as only service around. + +* add a mechanism we can drop capabilities from pid1 *before* transitioning + from initrd to host. i.e. before we transition into the slightly lower trust + domain that is the host systems we might want to get rid of some caps. + Example: CAP_SYS_BPF in the signed bpf loading logic above. (We already have + CapabilityBoundingSet= in system.conf, but that is enforced when pid 1 + initializes, rather then when it transitions to the next.) + +* maybe add a new standard slice where process that are started in the initrd + and stick around for the whole system runtime (i.e. root fs storage daemons, + the bpf loader daemon discussed above, and such) are placed. maybe + protected.slice or so? Then write docs that suggest that services like this + set Slice=protected.sice, RefuseManualStart=yes, RefuseManualStop=yes and a + couple of other things. + +* improve inode_same_at() to use AT_HANDLE_FID flag in name_to_handle_at() to + compare inode identity, rather than .st_ino – where available. Kernel FS + folks gave up on idea that inode numbers are fs-wide unique, and suggest + using the file handle/AT_HANDLE_FID instead. + * add feature to xopenat() that implements O_REGULAR in userspace: i.e. let's open the inode via O_PATH first, then validate its type, and then convert to proper fd via fd_reopen() @@ -202,9 +235,6 @@ Features: * systemd-nspawn should get the same SSH key support that vmspawn now has. -* insert the new pidfs inode number as a third field into PidRef, so that - PidRef are reasonably serializable without having to pass around fds. - * move documentation about our common env vars (SYSTEMD_LOG_LEVEL, SYSTEMD_PAGER, …) into a man page of its own, and just link it from our various man pages that so far embed the whole list again and again, in an @@ -242,22 +272,14 @@ Features: assert_ret(). Only export the stuff we are sure about, and keep some symbols internally where things are not clear whether we want other projects to use. -* machined: allow running in a per-user instance too, to allow unpriv - systemd-nspawn and systemd-vmspawn do something useful. (Alternatively: open - up system machined to unpriv client's registering their machines, and enforce - they come with some prefix or suffix that clarifies they are the - user's. i.e. when a user registers a machine it must be called - foobar.<username> or so.). - * importd/…: define per-user dirs for container/VM images too. * add a new specifier to unit files that figures out the DDI the unit file is from, tracing through overlayfs, DM, loopback block device. * importd/importctl - - import generator - port tar handling to libarchive - - add varlink interface + - complete varlink interface - download images into .v/ dirs * in os-release define a field that can be initialized at build time from @@ -314,12 +336,8 @@ Features: to read them from. This way the data doesn't remain in the SMBIOS blob during runtime, but only in the credentials fs. -* machined: make machine registration available via varlink to simplify - nspawn/vmspawn, and to have an extensible way to register VM/machine metadata - -* ssh-proxy: add support for "ssh machine/foobar" to automatically connect to - machined registered machine "foobar". Requires updating machined to track CID - and unix-export dir of containers. +* machined: optionally track nspawn unix-export/ runtime for each machined, and + then update systemd-ssh-proxy so that it can connect to that. * add a new ExecStart= flag that inserts the configured user's shell as first word in the command line. (maybe use character '.'). Usecase: tool such as @@ -478,10 +496,6 @@ Features: - kernel-install - systemd-mount (with PK so that desktop environments could use it to mount disks) -* in the service manager, pick up ERRNO= + BUSERROR= + VARLINKERROR= error - identifiers, and store them along with the exit status of a server and report - via "systemctl status". - * enumerate virtiofs devices during boot-up in a generator, and synthesize mounts for rootfs, /usr/, /home/, /srv/ and some others from it, depending on the "tag". (waits for: https://gitlab.com/virtio-fs/virtiofsd/-/issues/128) @@ -699,9 +713,6 @@ Features: security, resource management and cgroup settings can be enforced properly for all umh processes. -* systemd-shutdown: keep sending sd_notify() status updates immediately before - going down, in particular include the "reboot param" string. - * homed: when resizing an fs don't sync identity beforehand there might simply not be enough disk space for that. try to be defensive and sync only after resize. @@ -1270,10 +1281,6 @@ Features: * doc: prep a document explaining PID 1's internal logic, i.e. transactions, jobs, units -* bootspec: bring UEFI and userspace enumeration of bootspec entries back into - sync, i.e. parse out architecture field in sd-boot (currently only done in - userspace) - * automatically ignore threaded cgroups in cg_xyz(). * add linker script that implicitly adds symbol for build ID and new coredump @@ -2086,12 +2093,9 @@ Features: * EFI: - honor language efi variables for default language selection (if there are any?) - honor timezone efi variables for default timezone selection (if there are any?) - - change bootctl to be backed by systemd-bootd to control temporary and persistent default boot goal plus efi variables * bootctl - recognize the case when not booted on EFI -* bootctl,sd-boot: actually honour the "architecture" key - * bootctl: - show whether UEFI audit mode is available - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in index 1a5f0aec89..200c98eabe 100644 --- a/catalog/systemd.catalog.in +++ b/catalog/systemd.catalog.in @@ -119,6 +119,16 @@ Documentation: sd-login(3) A seat @SEAT_ID@ has been removed and is no longer available. +-- b2bcbaf5edf948e093ce50bbea0e81ec +Subject: The Secure Attention Key (SAK) was pressed on @SEAT_ID@ +Defined-By: systemd +Support: %SUPPORT_URL% +Documentation: man:systemd-logind.service(8) + +The Secure Attention Key (SAK), Ctrl+Alt+Shift+Esc, was pressed on @SEAT_ID@. + +Pressing the SAK indicates an explicit request by the user for the system to display a secure login dialog or greeter. + -- c7a787079b354eaaa9e77b371893cd27 Subject: Time change Defined-By: systemd diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index dcd296d17c..1352d31b91 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -686,7 +686,8 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as * `$SYSTEMD_VARLINK_LISTEN` – interpreted by some tools that provide a Varlink service. Takes a file system path: if specified the tool will listen on an `AF_UNIX` stream socket on the specified path in addition to whatever else it - would listen on. + would listen on. If set to "-" the tool will turn stdin/stdout into a Varlink + connection. `systemd-mountfsd`: diff --git a/docs/HACKING.md b/docs/HACKING.md index 51499d7f79..cbf2d45565 100644 --- a/docs/HACKING.md +++ b/docs/HACKING.md @@ -142,6 +142,50 @@ $ meson test -C build Happy hacking! +## Building distribution packages with mkosi + +To build distribution packages for a specific distribution and release without +building an actual image, the following command can be used: + +```sh +mkosi -d <distribution> -r <release> -t none -f +``` + +Afterwards the distribution packages will be located in `build/mkosi.output`. To +also build debuginfo packages, the following command can be used: + +```sh +mkosi -d <distribution> -r <release> -E WITH_DEBUG=1 -t none -f +``` + +To upgrade the systemd packages on the host system to the newer versions built +by mkosi, run the following: + +```sh +dnf upgrade build/mkosi.output/*.rpm # Fedora/CentOS +# TODO: Other distributions +``` + +To downgrade back to the old version shipped by the distribution, run the +following: + +```sh +dnf downgrade "systemd*" # Fedora/CentOS +# TODO: Other distributions +``` + +Additionally, for each pull request, the built distribution packages are +attached as CI artifacts to the pull request CI jobs, which means that users can +download and install them to test out if a pull request fixes the issue that +they reported. To download the packages from a pull request, click on the +`Checks` tab. Then click on the `mkosi` workflow in the list of workflows on the +left of the `Checks` page. Finally, scroll down to find the list of CI +artifacts. In this list of artifacts you can find artifacts containing +distribution packages. To install these, download the artifact which is a zip +archive, extract the zip archive to access the individual packages, and install +them with your package manager in the same way as described above for packages +that were built locally. + ## Templating engines in .in files Some source files are generated during build. We use two templating engines: diff --git a/docs/MEMORY_PRESSURE.md b/docs/MEMORY_PRESSURE.md index da1c9b2e4a..532f89456d 100644 --- a/docs/MEMORY_PRESSURE.md +++ b/docs/MEMORY_PRESSURE.md @@ -227,7 +227,7 @@ handling, it's typically sufficient to add a line such as: Other programming environments might have native APIs to watch memory pressure/low memory events. Most notable is probably GLib's -[GMemoryMonitor](https://developer-old.gnome.org/gio/stable/GMemoryMonitor.html). It +[GMemoryMonitor](https://docs.gtk.org/gio/iface.MemoryMonitor.html). It currently uses the per-system Linux PSI interface as the backend, but operates differently than the above: memory pressure events are picked up by a system service, which then propagates this through D-Bus to the applications. This is diff --git a/hwdb.d/60-input-id.hwdb b/hwdb.d/60-input-id.hwdb index b9d1ce0fc0..dfb035de2a 100644 --- a/hwdb.d/60-input-id.hwdb +++ b/hwdb.d/60-input-id.hwdb @@ -59,6 +59,10 @@ id-input:modalias:input:b0003v28bdp0078* id-input:modalias:input:b0003v04B3p301Ee0100-e0,1,2,4* ID_INPUT_POINTINGSTICK=1 +# Logitech G915 TKL Keyboard (Bluetooth) +id-input:modalias:input:b0005v046DpB35Fe0022* + ID_INPUT_MOUSE=0 + # Logitech Ultrathin Touch Mouse id-input:modalias:input:b0005v046DpB00De0700* ID_INPUT_MOUSE=1 diff --git a/hwdb.d/60-keyboard.hwdb b/hwdb.d/60-keyboard.hwdb index 81ffc69fa1..48aa92b433 100644 --- a/hwdb.d/60-keyboard.hwdb +++ b/hwdb.d/60-keyboard.hwdb @@ -1754,6 +1754,15 @@ evdev:atkbd:dmi:bvn*:bvr*:bd*:svn*:pn*:pvr*:rvnQuanta:rn30B7:rvr65.2B:* KEYBOARD_KEY_88=media # "quick play ########################################################### +# Redmi +########################################################### + +# RedmiBook Pro 15 2022 +evdev:atkbd:dmi:bvn*:bvr*:bd*:svnTIMI:pnRedmiBookPro152022:pvr* + KEYBOARD_KEY_9c=enter # KP_enter in the main area is wrong + KEYBOARD_KEY_dd=rightctrl # Right Ctrl is preferrable over Menu + +########################################################### # Samsung ########################################################### diff --git a/man/capsule@.service.xml b/man/capsule@.service.xml index aa5b1bbae3..f9c5455f3b 100644 --- a/man/capsule@.service.xml +++ b/man/capsule@.service.xml @@ -41,7 +41,7 @@ <listitem><para>The capsule service manager utilizes <varname>DynamicUser=</varname> (see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>) to allocate a new UID dynamically on invocation. The user name is automatically generated from the capsule - name, by prefixng <literal>p_</literal>. The UID is released when the service is terminated. The user + name, by prefixing <literal>c-</literal>. The UID is released when the service is terminated. The user service manager on the other hand operates under a statically allocated user ID that must be pre-existing, before the user service manager is invoked.</para></listitem> diff --git a/man/crypttab.xml b/man/crypttab.xml index 3aa809e667..8ffeaf7fcb 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -215,8 +215,11 @@ from the key file. See <citerefentry project='die-net'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry> for possible values and the default value of this option. This - option is ignored in plain encryption mode, as the key file - size is then given by the key size.</para> + option is ignored in plain encryption mode, where the key file + size is determined by the key size. It is also ignored when + the key file is used as a salt file for a FIDO2 token, as the + salt size in that case is defined by the FIDO2 specification + to be exactly 32 bytes.</para> <xi:include href="version-info.xml" xpointer="v188"/></listitem> </varlistentry> @@ -671,6 +674,26 @@ </varlistentry> <varlistentry> + <term><option>password-cache=yes|no|read-only</option></term> + + <listitem><para>Controls whether to use cache for passwords or security token PINs. + Takes a boolean or the special string <literal>read-only</literal>. Defaults to + <literal>yes</literal>.</para> + + <para>If set to <literal>read-only</literal>, the kernel keyring is checked for a + password/PIN before requesting one interactively. If set to <literal>yes</literal>, + in addition to checking the keyring, any password/PIN entered interactively is cached + in the keyring with a 2.5-minute timeout before being purged.</para> + + <para>Note that this option is not permitted for PKCS#11 security tokens. The reasoning + behind this is that PKCS#11 security tokens are usually configured to lock after being + supplied an invalid PIN multiple times, so using the cache might inadvertently lock the + token.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> <term><option>pkcs11-uri=</option></term> <listitem><para>Takes either the special value <literal>auto</literal> or an <ulink @@ -724,8 +747,7 @@ (configured in the line's third column) to operate. If not configured and the volume is of type LUKS2, the CID and the key are read from LUKS2 JSON token metadata instead. Use <citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry> - as simple tool for enrolling FIDO2 security tokens, compatible with this automatic mode, which is - only available for LUKS2 volumes.</para> + as simple tool for enrolling FIDO2 security tokens for LUKS2 volumes.</para> <para>Use <command>systemd-cryptenroll --fido2-device=list</command> to list all suitable FIDO2 security tokens currently plugged in, along with their device nodes.</para> @@ -1008,10 +1030,10 @@ and use this to determine which key to send, allowing a single listening socket to serve keys for multiple volumes. If the PKCS#11 logic is used (see above), the socket source name is picked in similar fashion, except that the literal string <literal>/cryptsetup-pkcs11/</literal> is used. And similarly for - FIDO2 (<literal>/cryptsetup-fido2/</literal>) and TPM2 (<literal>/cryptsetup-tpm2/</literal>). A different - path component is used so that services providing key material know that the secret key was not requested - directly, but instead an encrypted key that will be decrypted via the PKCS#11/FIDO2/TPM2 logic to acquire - the final secret key.</para> + FIDO2 (<literal>/cryptsetup-fido2-salt/</literal>) and TPM2 (<literal>/cryptsetup-tpm2/</literal>). + A different path component is used so that services providing key material know that the secret key was + not requested directly, but instead an encrypted key that will be decrypted via the PKCS#11/FIDO2/TPM2 + logic to acquire the final secret key.</para> </refsect1> <refsect1> diff --git a/man/logind.conf.xml b/man/logind.conf.xml index c52431fd41..66240b58fe 100644 --- a/man/logind.conf.xml +++ b/man/logind.conf.xml @@ -224,13 +224,14 @@ <term><varname>HandleLidSwitch=</varname></term> <term><varname>HandleLidSwitchExternalPower=</varname></term> <term><varname>HandleLidSwitchDocked=</varname></term> + <term><varname>HandleSecureAttentionKey=</varname></term> <listitem><para>Controls how logind shall handle the system power, reboot and sleep keys and the lid switch to trigger actions such as system power-off, reboot or suspend. Can be one of <literal>ignore</literal>, <literal>poweroff</literal>, <literal>reboot</literal>, <literal>halt</literal>, <literal>kexec</literal>, <literal>suspend</literal>, <literal>hibernate</literal>, <literal>hybrid-sleep</literal>, <literal>suspend-then-hibernate</literal>, <literal>sleep</literal>, <literal>lock</literal>, and - <literal>factory-reset</literal>. If <literal>ignore</literal>, <command>systemd-logind</command> + <literal>factory-reset</literal>, <literal>secure-attention-key</literal>. If <literal>ignore</literal>, <command>systemd-logind</command> will never handle these keys. If <literal>lock</literal>, all running sessions will be screen-locked; otherwise, the specified action will be taken in the respective event. Only input devices with the <literal>power-switch</literal> udev tag will be watched for key/lid switch @@ -251,7 +252,8 @@ system is inserted in a docking station, or if more than one display is connected, the action specified by <varname>HandleLidSwitchDocked=</varname> occurs; if the system is on external power the action (if any) specified by <varname>HandleLidSwitchExternalPower=</varname> occurs; otherwise the - <varname>HandleLidSwitch=</varname> action occurs.</para> + <varname>HandleLidSwitch=</varname> action occurs. + <varname>HandleSecureAttentionKey=</varname> defaults to <literal>secure-attention-key</literal></para> <para>A different application may disable logind's handling of system power and sleep keys and the lid switch by taking a low-level inhibitor lock @@ -262,7 +264,7 @@ to take over suspend and hibernation handling, and to use their own configuration mechanisms. If a low-level inhibitor lock is taken, logind will not take any action when that key or switch is triggered and the <varname>Handle*=</varname> - settings are irrelevant.</para> + settings are irrelevant, except for <varname>HandleSecureAttentionKey=</varname>, which is always handled since its addition in v257.</para> <xi:include href="version-info.xml" xpointer="v184"/></listitem> </varlistentry> @@ -393,6 +395,20 @@ <xi:include href="version-info.xml" xpointer="v252"/></listitem> </varlistentry> + + <varlistentry> + <term><varname>DesignatedMaintenanceTime=</varname></term> + + <listitem> + <para> + Specifies a default calendar event for scheduled shutdowns. So when using e.g. the command + <command>shutdown -r</command> to reboot the system without specifying a timeout, logind would + use the configured calendar event instead. For details about the syntax of calendar events, see + <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>. + </para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/man/org.freedesktop.login1.xml b/man/org.freedesktop.login1.xml index d9b9b0e1b3..cba371ca9e 100644 --- a/man/org.freedesktop.login1.xml +++ b/man/org.freedesktop.login1.xml @@ -169,6 +169,8 @@ node /org/freedesktop/login1 { SetWallMessage(in s wall_message, in b enable); signals: + SecureAttentionKey(s seat_id, + o object_path); SessionNew(s session_id, o object_path); SessionRemoved(s session_id, @@ -244,6 +246,8 @@ node /org/freedesktop/login1 { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s HandleLidSwitchDocked = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s HandleSecureAttentionKey = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t HoldoffTimeoutUSec = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s IdleAction = '...'; @@ -253,9 +257,10 @@ node /org/freedesktop/login1 { readonly b PreparingForShutdown = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b PreparingForSleep = ...; - @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly (st) ScheduledShutdown = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + readonly s DesignatedMaintenanceTime = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly b Docked = ...; readonly b LidClosed = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("false") @@ -295,6 +300,10 @@ node /org/freedesktop/login1 { <!--property HandleHibernateKeyLongPress is not documented!--> + <!--property HandleSecureAttentionKey is not documented!--> + + <!--property DesignatedMaintenanceTime is not documented!--> + <!--property StopIdleSessionUSec is not documented!--> <!--Autogenerated cross-references for systemd.directives, do not edit--> @@ -427,6 +436,8 @@ node /org/freedesktop/login1 { <variablelist class="dbus-method" generated="True" extra-ref="SetWallMessage()"/> + <variablelist class="dbus-signal" generated="True" extra-ref="SecureAttentionKey()"/> + <variablelist class="dbus-signal" generated="True" extra-ref="SessionNew()"/> <variablelist class="dbus-signal" generated="True" extra-ref="SessionRemoved()"/> @@ -505,6 +516,8 @@ node /org/freedesktop/login1 { <variablelist class="dbus-property" generated="True" extra-ref="HandleLidSwitchDocked"/> + <variablelist class="dbus-property" generated="True" extra-ref="HandleSecureAttentionKey"/> + <variablelist class="dbus-property" generated="True" extra-ref="HoldoffTimeoutUSec"/> <variablelist class="dbus-property" generated="True" extra-ref="IdleAction"/> @@ -517,6 +530,8 @@ node /org/freedesktop/login1 { <variablelist class="dbus-property" generated="True" extra-ref="ScheduledShutdown"/> + <variablelist class="dbus-property" generated="True" extra-ref="DesignatedMaintenanceTime"/> + <variablelist class="dbus-property" generated="True" extra-ref="Docked"/> <variablelist class="dbus-property" generated="True" extra-ref="LidClosed"/> @@ -688,7 +703,10 @@ node /org/freedesktop/login1 { <literal>challenge</literal> is returned, the operation is available but only after authorization.</para> <para><function>ScheduleShutdown()</function> schedules a shutdown operation <varname>type</varname> at - time <varname>usec</varname> in microseconds since the UNIX epoch. <varname>type</varname> can be one + time <varname>usec</varname> in microseconds since the UNIX epoch. Alternatively, if + <varname>usec</varname> <literal>UINT64_MAX</literal> and a maintenance window is + configured, <filename>systemd-logind</filename> will use the next time of the maintenance window + instead. <varname>type</varname> can be one of <literal>poweroff</literal>, <literal>dry-poweroff</literal>, <literal>reboot</literal>, <literal>dry-reboot</literal>, <literal>halt</literal>, and <literal>dry-halt</literal>. (The <literal>dry-</literal> variants do not actually execute the shutdown action.) @@ -725,6 +743,10 @@ node /org/freedesktop/login1 { <para>Whenever the inhibition state or idle hint changes, <function>PropertyChanged</function> signals are sent out to which clients can subscribe.</para> + <para>The <function>SecureAttentionKey()</function> signal is sent when the user presses Ctrl+Alt+Shift+Esc to + request the login manager to display the greeter, for instance in the case of a deadlocked compositor. + </para> + <para>The <function>SessionNew()</function>, <function>SessionRemoved()</function>, <function>UserNew()</function>, <function>UserRemoved()</function>, <function>SeatNew()</function>, and <function>SeatRemoved()</function> signals are sent each time a session is created or removed, a user @@ -1579,8 +1601,11 @@ node /org/freedesktop/login1/session/1 { <function>CreateSessionWithPIDFD()</function> were added in version 255.</para> <para><function>Sleep()</function>, <function>CanSleep()</function>, - <varname>SleepOperation</varname>, and + <varname>SleepOperation</varname>, + <varname>DesignatedMaintenanceTime</varname>, and <function>ListSessionsEx()</function> were added in version 256.</para> + <para><varname>HandleSecureAttentionKey</varname>, and + <function>SecureAttentionKey()</function> were added in version 257.</para> </refsect2> <refsect2> <title>Session Objects</title> diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index b0b45097e3..31e6194bec 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -2745,6 +2745,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { readonly s FileDescriptorStorePreserve = '...'; readonly s StatusText = '...'; readonly i StatusErrno = ...; + readonly s StatusBusError = '...'; + readonly s StatusVarlinkError = '...'; readonly s Result = '...'; readonly s ReloadResult = '...'; readonly s CleanResult = '...'; @@ -3205,6 +3207,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateTmp = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s PrivateTmpEx = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateDevices = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b ProtectClock = ...; @@ -3404,8 +3408,6 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { <!--property FileDescriptorStorePreserve is not documented!--> - <!--property StatusErrno is not documented!--> - <!--property ReloadResult is not documented!--> <!--property CleanResult is not documented!--> @@ -3816,6 +3818,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { <!--property PrivateTmp is not documented!--> + <!--property PrivateTmpEx is not documented!--> + <!--property PrivateDevices is not documented!--> <!--property ProtectClock is not documented!--> @@ -4026,6 +4030,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { <variablelist class="dbus-property" generated="True" extra-ref="StatusErrno"/> + <variablelist class="dbus-property" generated="True" extra-ref="StatusBusError"/> + + <variablelist class="dbus-property" generated="True" extra-ref="StatusVarlinkError"/> + <variablelist class="dbus-property" generated="True" extra-ref="Result"/> <variablelist class="dbus-property" generated="True" extra-ref="ReloadResult"/> @@ -4500,6 +4508,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/> + <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmpEx"/> + <variablelist class="dbus-property" generated="True" extra-ref="PrivateDevices"/> <variablelist class="dbus-property" generated="True" extra-ref="ProtectClock"/> @@ -4732,11 +4742,11 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { process is currently running while the latter possible contains information collected from the last run even if the process is no longer around.</para> - <para><varname>StatusText</varname> contains the status text passed to the service manager via a call - to - <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>. - This may be used by services to inform the service manager about its internal state with a nice - explanatory string.</para> + <para><varname>StatusText</varname>, <varname>StatusErrno</varname>, <varname>StatusBusError</varname>, + and <varname>StatusVarlinkError</varname> contain the status text, the error number, + and the D-Bus/Varlink error name passed to the service manager via + <citerefentry><refentrytitle>sd_notify</refentrytitle><manvolnum>3</manvolnum></citerefentry>, + respectively. They may be used by services to inform the service manager about its internal state.</para> <para><varname>Result</varname> encodes the execution result of the last run of the service. It is useful to determine the reason a service failed if it is in the <literal>failed</literal> state (see @@ -5322,6 +5332,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateTmp = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s PrivateTmpEx = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateDevices = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b ProtectClock = ...; @@ -5945,6 +5957,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { <!--property PrivateTmp is not documented!--> + <!--property PrivateTmpEx is not documented!--> + <!--property PrivateDevices is not documented!--> <!--property ProtectClock is not documented!--> @@ -6609,6 +6623,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket { <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/> + <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmpEx"/> + <variablelist class="dbus-property" generated="True" extra-ref="PrivateDevices"/> <variablelist class="dbus-property" generated="True" extra-ref="ProtectClock"/> @@ -7295,6 +7311,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateTmp = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s PrivateTmpEx = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateDevices = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b ProtectClock = ...; @@ -7844,6 +7862,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { <!--property PrivateTmp is not documented!--> + <!--property PrivateTmpEx is not documented!--> + <!--property PrivateDevices is not documented!--> <!--property ProtectClock is not documented!--> @@ -8420,6 +8440,8 @@ node /org/freedesktop/systemd1/unit/home_2emount { <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/> + <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmpEx"/> + <variablelist class="dbus-property" generated="True" extra-ref="PrivateDevices"/> <variablelist class="dbus-property" generated="True" extra-ref="ProtectClock"/> @@ -9229,6 +9251,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateTmp = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s PrivateTmpEx = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b PrivateDevices = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly b ProtectClock = ...; @@ -9764,6 +9788,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { <!--property PrivateTmp is not documented!--> + <!--property PrivateTmpEx is not documented!--> + <!--property PrivateDevices is not documented!--> <!--property ProtectClock is not documented!--> @@ -10326,6 +10352,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap { <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmp"/> + <variablelist class="dbus-property" generated="True" extra-ref="PrivateTmpEx"/> + <variablelist class="dbus-property" generated="True" extra-ref="PrivateDevices"/> <variablelist class="dbus-property" generated="True" extra-ref="ProtectClock"/> @@ -12015,7 +12043,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ <function>DumpUnitFileDescriptorStore()</function> were added in version 254.</para> <para><function>StartAuxiliaryScope()</function>, <varname>ShutdownStartTimestamp</varname>, - <varname>ShutdownStartTimestampMonotonic</varname> and + <varname>ShutdownStartTimestampMonotonic</varname>, and <varname>SoftRebootsCount</varname> were added in version 256.</para> </refsect2> <refsect2> @@ -12070,6 +12098,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ <varname>MemoryZSwapWriteback</varname>, <varname>ExecMainHandoffTimestampMonotonic</varname>, and <varname>ExecMainHandoffTimestamp</varname> were added in version 256.</para> + <para><varname>StatusBusError</varname>, + <varname>StatusVarlinkError</varname>, and + <varname>PrivateTmpEx</varname> were added in version 257.</para> </refsect2> <refsect2> <title>Socket Unit Objects</title> @@ -12106,6 +12137,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ <varname>EffectiveTasksMax</varname>, <varname>MemoryZSwapWriteback</varname>, and <varname>PassFileDescriptorsToExec</varname> were added in version 256.</para> + <para><varname>PrivateTmpEx</varname> was added in version 257.</para> </refsect2> <refsect2> <title>Mount Unit Objects</title> @@ -12139,6 +12171,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ <varname>EffectiveMemoryMax</varname>, <varname>EffectiveTasksMax</varname>, and <varname>MemoryZSwapWriteback</varname> were added in version 256.</para> + <para><varname>PrivateTmpEx</varname> was added in version 257.</para> </refsect2> <refsect2> <title>Swap Unit Objects</title> @@ -12172,6 +12205,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \ <varname>EffectiveMemoryMax</varname>, <varname>EffectiveTasksMax</varname>, and <varname>MemoryZSwapWriteback</varname> were added in version 256.</para> + <para><varname>PrivateTmpEx</varname> was added in version 257.</para> </refsect2> <refsect2> <title>Slice Unit Objects</title> diff --git a/man/repart.d.xml b/man/repart.d.xml index 52e6b97240..804cb804b2 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -362,12 +362,14 @@ <varlistentry> <term><varname>CopyBlocks=</varname></term> - <listitem><para>Takes a path to a regular file, block device node or directory, or the special value - <literal>auto</literal>. If specified and the partition is newly created, the data from the specified - path is written to the newly created partition, on the block level. If a directory is specified, the - backing block device of the file system the directory is on is determined, and the data read directly - from that. This option is useful to efficiently replicate existing file systems onto new partitions - on the block level — for example to build a simple OS installer or an OS image builder.</para> + <listitem><para>Takes a path to a regular file, block device node, char device node or directory, or + the special value <literal>auto</literal>. If specified and the partition is newly created, the data + from the specified path is written to the newly created partition, on the block level. If a directory + is specified, the backing block device of the file system the directory is on is determined, and the + data read directly from that. This option is useful to efficiently replicate existing file systems + onto new partitions on the block level — for example to build a simple OS installer or an OS image + builder. Specify <filename>/dev/urandom</filename> as value to initialize a partition with random + data.</para> <para>If the special value <literal>auto</literal> is specified, the source to copy from is automatically picked up from the running system (or the image specified with @@ -819,6 +821,7 @@ <xi:include href="standard-specifiers.xml" xpointer="m"/> <xi:include href="standard-specifiers.xml" xpointer="M"/> <xi:include href="standard-specifiers.xml" xpointer="o"/> + <xi:include href="standard-specifiers.xml" xpointer="q"/> <xi:include href="standard-specifiers.xml" xpointer="v"/> <xi:include href="standard-specifiers.xml" xpointer="w"/> <xi:include href="standard-specifiers.xml" xpointer="W"/> diff --git a/man/rules/meson.build b/man/rules/meson.build index 9b8a29c564..fda14d55bd 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -953,6 +953,7 @@ manpages = [ ['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'], ['systemd-hwdb', '8', [], 'ENABLE_HWDB'], ['systemd-id128', '1', [], ''], + ['systemd-import-generator', '8', [], ''], ['systemd-importd.service', '8', ['systemd-importd'], 'ENABLE_IMPORTD'], ['systemd-inhibit', '1', [], ''], ['systemd-initctl.service', diff --git a/man/sd_notify.xml b/man/sd_notify.xml index 35c450b128..f04251bd19 100644 --- a/man/sd_notify.xml +++ b/man/sd_notify.xml @@ -258,13 +258,21 @@ <term>BUSERROR=…</term> <listitem><para>If a service fails, the D-Bus error-style error code. Example: - <literal>BUSERROR=org.freedesktop.DBus.Error.TimedOut</literal>. Note that this assignment is - currently not used by <command>systemd</command>.</para> + <literal>BUSERROR=org.freedesktop.DBus.Error.TimedOut</literal>.</para> <xi:include href="version-info.xml" xpointer="v233"/></listitem> </varlistentry> <varlistentry> + <term>VARLINKERROR=…</term> + + <listitem><para>If a service fails, the Varlink error-style error code. Example: + <literal>VARLINKERROR=org.varlink.service.InvalidParameter</literal>.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> <term>EXIT_STATUS=…</term> <listitem><para>The exit status of a service or the manager itself. Note that diff --git a/man/systemctl.xml b/man/systemctl.xml index 70fd91f45a..768a30627f 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -2862,7 +2862,9 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err which should adhere to the syntax documented in <citerefentry project='man-pages'><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> section "PARSING TIMESTAMPS". Specially, if <literal>show</literal> is given, the currently scheduled - action will be shown, which can be canceled by passing an empty string or <literal>cancel</literal>.</para> + action will be shown, which can be canceled by passing an empty string or <literal>cancel</literal>. + <literal>auto</literal> will schedule the action according to maintenance window or one minute in + the future.</para> <xi:include href="version-info.xml" xpointer="v254"/> </listitem> diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index a47866ba61..eadf5a4ace 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -310,7 +310,9 @@ <filename>/dev/hidraw1</filename>). Alternatively the special value <literal>auto</literal> may be specified, in order to automatically determine the device node of a currently plugged in security token (of which there must be exactly one). This automatic discovery is unsupported if - <option>--fido2-device=</option> option is also specified.</para> + <option>--fido2-device=</option> option is also specified. Note that currently FIDO2 devices + enrolled without an accompanying LUKS2 token (i.e. <option>--fido2-parameters-in-header=no</option>) + cannot be used for unlocking.</para> <xi:include href="version-info.xml" xpointer="v253"/></listitem> </varlistentry> @@ -402,6 +404,30 @@ </varlistentry> <varlistentry> + <term><option>--fido2-salt-file=<replaceable>PATH</replaceable></option></term> + + <listitem><para>When enrolling a FIDO2 security token, specifies the path to a file or an + <constant>AF_UNIX</constant> socket from which we should read the salt value to be used in the + HMAC operation performed by the FIDO2 security token. If this option is not specified, the salt + will be randomly generated.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> + <term><option>--fido2-parameters-in-header=<replaceable>BOOL</replaceable></option></term> + + <listitem><para>When enrolling a FIDO2 security token, controls whether to store FIDO2 + parameters in a token in the LUKS2 superblock. Defaults to <literal>yes</literal>. + If set to <literal>no</literal>, the <option>fido2-cid=</option> option has to be specified manually + in the respective <filename>/etc/crypttab</filename> line along with a key file. See + <citerefentry><refentrytitle>crypttab</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for details.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> <term><option>--fido2-with-client-pin=<replaceable>BOOL</replaceable></option></term> <listitem><para>When enrolling a FIDO2 security token, controls whether to require the user to enter diff --git a/man/systemd-cryptsetup.xml b/man/systemd-cryptsetup.xml index 676a38a763..1c2db11a45 100644 --- a/man/systemd-cryptsetup.xml +++ b/man/systemd-cryptsetup.xml @@ -94,8 +94,9 @@ <listitem><para>If the <varname>try-empty-password</varname> option is specified then unlocking the volume with an empty password is attempted.</para></listitem> - <listitem><para>The kernel keyring is then checked for a suitable cached password from previous - attempts.</para></listitem> + <listitem><para>If the <varname>password-cache=</varname> option is set to <literal>yes</literal> or + <literal>read-only</literal>, the kernel keyring is then checked for a suitable cached password from + previous attempts.</para></listitem> <listitem><para>Finally, the user is queried for a password, possibly multiple times, unless the <varname>headless</varname> option is set.</para></listitem> diff --git a/man/systemd-import-generator.xml b/man/systemd-import-generator.xml new file mode 100644 index 0000000000..108509d7d4 --- /dev/null +++ b/man/systemd-import-generator.xml @@ -0,0 +1,194 @@ +<?xml version="1.0"?> +<!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [ +<!ENTITY % entities SYSTEM "custom-entities.ent" > +%entities; +]> +<!-- SPDX-License-Identifier: LGPL-2.1-or-later --> +<refentry id="systemd-import-generator" + xmlns:xi="http://www.w3.org/2001/XInclude"> + + <refentryinfo> + <title>systemd-import-generator</title> + <productname>systemd</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-import-generator</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-import-generator</refname> + <refpurpose>Generator for automatically downloading disk images at boot</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>/usr/lib/systemd/system-generators/systemd-import-generator</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>systemd-import-generator</command> may be used to automatically download disk images + (tarballs or DDIs) via + <citerefentry><refentrytitle>systemd-importd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + at boot, based on parameters on the kernel command line or via system credentials. This is useful for + automatically deploying an + <citerefentry><refentrytitle>systemd-confext</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-sysext</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-nspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry>/ + <citerefentry><refentrytitle>systemd-vmspawn</refentrytitle><manvolnum>1</manvolnum></citerefentry> or + <citerefentry><refentrytitle>systemd-portabled.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + image at boot. This provides functionality equivalent to + <citerefentry><refentrytitle>importctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, but + accessible via the kernel command line and system credentials.</para> + + <para><filename>systemd-import-generator</filename> implements + <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para> + </refsect1> + + <refsect1> + <title>Kernel Command Line</title> + + <para><filename>systemd-import-generator</filename> understands the following + <citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry> + parameters:</para> + + <variablelist class='kernel-commandline-options'> + <varlistentry> + <term><varname>systemd.pull=</varname></term> + + <listitem><para>This option takes a colon separate triplet of option string, local target image name + and remote URL. The local target image name can be specified as an empty string, in which case the + name is derived from the specified remote URL. The remote URL must using the + <literal>http://</literal>, <literal>https://</literal>, <literal>file://</literal> schemes. The + option string itself is a comma separated list of options:</para> + + <variablelist> + <varlistentry> + <term>rw</term> + <term>ro</term> + + <listitem><para>Controls whether to mark the local image as read-only. If not + specified read-only defaults to off.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> + <term>verify=</term> + + <listitem><para>Controls whether to cryptographically validate the download before installing it + in place. Takes one of <literal>no</literal>, <literal>checksum</literal> or + <literal>signature</literal> (the latter being the default if not specified). For details see the + <option>--verify=</option> of + <citerefentry><refentrytitle>importctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> + <term>sysext</term> + <term>confext</term> + <term>machine</term> + <term>portable</term> + + <listitem><para>Controls the image class to download, and thus ultimately the target directory + for the image, depending on this choice the target directory + <filename>/var/lib/extensions/</filename>, <filename>/var/lib/confexts/</filename>, + <filename>/var/lib/machines/</filename> or <filename>/var/lib/portables/</filename> is + selected.</para> + + <para>Specification of exactly one of these options is mandatory.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> + <term>tar</term> + <term>raw</term> + + <listitem><para>Controls the type of resource to download, i.e. a (possibly compressed) tarball + that needs to be unpacked into a file system tree, or (possibly compressed) raw disk image (DDI).</para> + + <para>Specification of exactly one of these options is mandatory.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + </variablelist> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> + <term><varname>systemd.pull.success_action=</varname></term> + <term><varname>systemd.pull.failure_action=</varname></term> + + <listitem><para>Controls whether to execute an action such as reboot, power-off and similar after + completing the download successfully, or unsuccessfully. See + <varname>SuccessAction=</varname>/<varname>FailureAction=</varname> on + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> for + details about the available actions. If not specified no action is taken, and the system will + continue to boot normally.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Credentials</title> + + <para><command>systemd-import-generator</command> supports the system credentials logic. The following + credentials are used when passed in:</para> + + <variablelist class='system-credentials'> + <varlistentry> + <term><varname>import.pull</varname></term> + + <listitem><para>This credential should be a text file, with each line referencing one download + operation. Each line should follow the same format as the value of the + <varname>systemd.pull=</varname> kernel command line option described above.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Examples</title> + + <example> + <title>Download Configuration Extension</title> + + <programlisting>systemd.pull=raw,confext::https://example.com/myconfext.raw.gz</programlisting> + + <para>With a kernel command line option like the above a configuration extension DDI is downloaded + automatically at boot from the specified URL, validated cryptographically, uncompressed and installed.</para> + </example> + + <example> + <title>Download System Extension (Without Validation)</title> + + <programlisting>systemd.pull=tar,sysext,verify=no::https://example.com/mysysext.tar.gz</programlisting> + + <para>With a kernel command line option like the above a system extension tarball is downloaded + automatically at boot from the specified URL, uncompressed and installed – without any cryptographic + validation. This is useful for development purposes in virtual machines and containers. Warning: do not + deploy a system with validation disabled like this!</para> + </example> + </refsect1> + + <refsect1> + <title>See Also</title> + <para><simplelist type="inline"> + <member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member> + <member><citerefentry><refentrytitle>systemd-importd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member> + <member><citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry></member> + <member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member> + <member><citerefentry><refentrytitle>importctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member> + </simplelist></para> + </refsect1> +</refentry> diff --git a/man/systemd-inhibit.xml b/man/systemd-inhibit.xml index a6dbb06c36..5299719525 100644 --- a/man/systemd-inhibit.xml +++ b/man/systemd-inhibit.xml @@ -114,6 +114,7 @@ acquiring one.</para></listitem> </varlistentry> + <xi:include href="standard-options.xml" xpointer="no-ask-password" /> <xi:include href="standard-options.xml" xpointer="no-pager" /> <xi:include href="standard-options.xml" xpointer="no-legend" /> <xi:include href="standard-options.xml" xpointer="help" /> diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml index 5c68aa51d5..408b7d0577 100644 --- a/man/systemd-tmpfiles.xml +++ b/man/systemd-tmpfiles.xml @@ -169,7 +169,7 @@ <para>It is recommended to first run this command in combination with <option>--dry-run</option> (see below) to verify which files and directories will be deleted.</para> - <para><emphasis>Warning!</emphasis> This is is usually not the command you want! In most cases + <para><emphasis>Warning!</emphasis> This is usually not the command you want! In most cases <option>--remove</option> is what you are looking for.</para> <xi:include href="version-info.xml" xpointer="v256"/></listitem> diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 9e621b9aa3..7a2fc76b65 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -675,8 +675,8 @@ of IPC objects and temporary files created by the executed processes is bound to the runtime of the service, and hence the lifetime of the dynamic user/group. Since <filename>/tmp/</filename> and <filename>/var/tmp/</filename> are usually the only world-writable directories on a system, unless - <varname>PrivateTmp=</varname> is manually enabled, those directories will be placed on a private - tmpfs filesystem, as this ensures that a unit making use of dynamic user/group allocation cannot + <varname>PrivateTmp=</varname> is manually set to <literal>true</literal>, <literal>disconnected</literal> + would be implied. This ensures that a unit making use of dynamic user/group allocation cannot leave files around after unit termination. Furthermore <varname>NoNewPrivileges=</varname> and <varname>RestrictSUIDSGID=</varname> are implicitly enabled (and cannot be disabled), to ensure that processes invoked cannot take benefit or create SUID/SGID @@ -1748,20 +1748,27 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting> <varlistentry> <term><varname>PrivateTmp=</varname></term> - <listitem><para>Takes a boolean argument. If true, sets up a new file system namespace for the - executed processes and mounts private <filename>/tmp/</filename> and <filename>/var/tmp/</filename> - directories inside it that are not shared by processes outside of the namespace. This is useful to - secure access to temporary files of the process, but makes sharing between processes via - <filename>/tmp/</filename> or <filename>/var/tmp/</filename> impossible. If true, all temporary files - created by a service in these directories will be removed after the service is stopped. Defaults to - false. It is possible to run two or more units within the same private <filename>/tmp/</filename> and - <filename>/var/tmp/</filename> namespace by using the <varname>JoinsNamespaceOf=</varname> directive, - see <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> - for details. This setting is implied if <varname>DynamicUser=</varname> is set. For this setting, the - same restrictions regarding mount propagation and privileges apply as for - <varname>ReadOnlyPaths=</varname> and related calls, see above. Enabling this setting has the side - effect of adding <varname>Requires=</varname> and <varname>After=</varname> dependencies on all mount - units necessary to access <filename>/tmp/</filename> and <filename>/var/tmp/</filename>. Moreover an + <listitem><para>Takes a boolean argument, or <literal>disconnected</literal>. If enabled, a new + file system namespace will be set up for the executed processes, and <filename>/tmp/</filename> + and <filename>/var/tmp/</filename> directories inside it are not shared with processes outside of + the namespace, plus all temporary files created by a service in these directories will be removed after + the service is stopped. If <literal>true</literal>, the backing storage of the private temporary directories + will remain on the host's <filename>/tmp/</filename> and <filename>/var/tmp/</filename> directories. + If <literal>disconnected</literal>, the directories will be backed by a completely new tmpfs instance, + meaning that the storage is fully disconnected from the host namespace. Defaults to false.</para> + + <para>This setting is useful to secure access to temporary files of the process, but makes sharing + between processes via <filename>/tmp/</filename> or <filename>/var/tmp/</filename> impossible. + If not set to <literal>disconnected</literal>, it is possible to run two or more units within + the same private <filename>/tmp/</filename> and <filename>/var/tmp/</filename> namespace by using + the <varname>JoinsNamespaceOf=</varname> directive, see + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for details. This setting is implied if <varname>DynamicUser=</varname> is set. For this setting, + the same restrictions regarding mount propagation and privileges apply as for + <varname>ReadOnlyPaths=</varname> and related calls, see above. If set to <literal>true</literal> + (as opposed to <literal>disconnected</literal>), this has the side effect of adding + <varname>Requires=</varname> and <varname>After=</varname> dependencies on all mount units necessary + to access <filename>/tmp/</filename> and <filename>/var/tmp/</filename> on the host. Moreover an implicitly <varname>After=</varname> ordering on <citerefentry><refentrytitle>systemd-tmpfiles-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> is added.</para> diff --git a/man/systemd.journal-fields.xml b/man/systemd.journal-fields.xml index bf5ac09cf6..b7a72af08b 100644 --- a/man/systemd.journal-fields.xml +++ b/man/systemd.journal-fields.xml @@ -272,11 +272,10 @@ <varlistentry> <term><varname>_SOURCE_REALTIME_TIMESTAMP=</varname></term> - <term><varname>_SOURCE_MONOTONIC_TIMESTAMP=</varname></term> <listitem> <para>The earliest trusted timestamp of the message, if any is known that is different from - the reception time of the journal. These are the <constant>CLOCK_REALTIME</constant> and - <constant>CLOCK_MONOTONIC</constant> clocks in microseconds, formatted as decimal strings.</para> + the reception time of the journal. The timestamp is in the <constant>CLOCK_REALTIME</constant> + clock in microseconds, formatted as decimal strings.</para> </listitem> </varlistentry> @@ -284,7 +283,7 @@ <term><varname>_SOURCE_BOOTTIME_TIMESTAMP=</varname></term> <listitem> <para>The earliest trusted timestamp of the message in <constant>CLOCK_BOOTTIME</constant> clock. - For details, refer to <varname>_SOURCE_MONOTONIC_TIMESTAMP=</varname>.</para> + For details, refer to <varname>_SOURCE_REALTIME_TIMESTAMP=</varname>.</para> <xi:include href="version-info.xml" xpointer="v257"/> </listitem> diff --git a/man/systemd.system-credentials.xml b/man/systemd.system-credentials.xml index d9fbae25ee..f8c27d04ac 100644 --- a/man/systemd.system-credentials.xml +++ b/man/systemd.system-credentials.xml @@ -415,6 +415,16 @@ <xi:include href="version-info.xml" xpointer="v256"/> </listitem> </varlistentry> + + <varlistentry> + <term><varname>import.pull</varname></term> + <listitem> + <para>Specified disk images (tarballs and DDIs) to automatically download and install at boot. For details see + <citerefentry><refentrytitle>systemd-import-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + + <xi:include href="version-info.xml" xpointer="v257"/> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/man/sysusers.d.xml b/man/sysusers.d.xml index 9d5a03e7ee..f5f12381d6 100644 --- a/man/sysusers.d.xml +++ b/man/sysusers.d.xml @@ -278,6 +278,7 @@ r - 500-900 <xi:include href="standard-specifiers.xml" xpointer="m"/> <xi:include href="standard-specifiers.xml" xpointer="M"/> <xi:include href="standard-specifiers.xml" xpointer="o"/> + <xi:include href="standard-specifiers.xml" xpointer="q"/> <xi:include href="standard-specifiers.xml" xpointer="T"/> <xi:include href="standard-specifiers.xml" xpointer="v"/> <xi:include href="standard-specifiers.xml" xpointer="V"/> diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index c89706862f..15027def60 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -306,7 +306,7 @@ L /tmp/foobar - - - - /dev/null</programlisting> argument is omitted, symlinks to files with the same name residing in the directory <filename>/usr/share/factory/</filename> are created. Note - that permissions and ownership on symlinks are ignored. + that permissions on symlinks are ignored. </para></listitem> </varlistentry> @@ -588,8 +588,8 @@ w- /proc/sys/vm/swappiness - - - - 10</programlisting></para> <citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>8</manvolnum></citerefentry> is used. For <varname>z</varname> and <varname>Z</varname> lines, when omitted or when set to <literal>-</literal>, the file ownership will not be modified. These parameters are ignored for - <varname>x</varname>, <varname>r</varname>, <varname>R</varname>, <varname>L</varname>, - <varname>t</varname>, and <varname>a</varname> lines.</para> + <varname>x</varname>, <varname>r</varname>, <varname>R</varname>, <varname>t</varname>, + and <varname>a</varname> lines.</para> <para>This field should generally only reference system users/groups, i.e. users/groups that are guaranteed to be resolvable during early boot. If this field references users/groups that only become @@ -764,6 +764,7 @@ d /tmp/foo/bar - - - bmA:1h -</programlisting></para> <xi:include href="standard-specifiers.xml" xpointer="m"/> <xi:include href="standard-specifiers.xml" xpointer="M"/> <xi:include href="standard-specifiers.xml" xpointer="o"/> + <xi:include href="standard-specifiers.xml" xpointer="q"/> <row> <entry><literal>%S</literal></entry> <entry>System or user state directory</entry> diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml index f21e513cb0..0ecd168c33 100644 --- a/man/varlinkctl.xml +++ b/man/varlinkctl.xml @@ -71,16 +71,23 @@ <itemizedlist> <listitem><para>A Varlink service reference starting with the <literal>unix:</literal> string, followed - by an absolute <constant>AF_UNIX</constant> socket path, or by <literal>@</literal> and an arbitrary string - (the latter for referencing sockets in the abstract namespace).</para></listitem> + by an absolute <constant>AF_UNIX</constant> socket path, or by <literal>@</literal> and an arbitrary + string (the latter for referencing sockets in the abstract namespace). In this case a stream socket + connection is made to the specified socket.</para></listitem> <listitem><para>A Varlink service reference starting with the <literal>exec:</literal> string, followed - by an absolute path of a binary to execute.</para></listitem> + by an absolute path of a binary to execute. In this case the specified process is forked off locally, + with a connected stream socket passed in.</para></listitem> - <listitem><para>A Varlink service reference starting with the <literal>ssh:</literal> string, followed + <listitem><para>A Varlink service reference starting with the <literal>ssh-unix:</literal> string, followed by an SSH host specification, followed by <literal>:</literal>, followed by an absolute <constant>AF_UNIX</constant> socket path. (This requires OpenSSH 9.4 or newer on the server side, abstract namespace sockets are not supported.)</para></listitem> + + <listitem><para>A Varlink service reference starting with the <literal>ssh-exec:</literal> string, + followed by an SSH host specification, followed by <literal>:</literal>, followed by a command line. In + this case the command is invoked and the Varlink protocol is spoken on the standard input and output of + the invoked command.</para></listitem> </itemizedlist> <para>For convenience these two simpler (redundant) service address syntaxes are also supported:</para> @@ -250,6 +257,20 @@ </listitem> </varlistentry> + <varlistentry> + <term><option>--graceful=</option></term> + + <listitem> + <para>Takes a qualified Varlink error name (i.e. an interface name, suffixed by an error name, + separated by a dot; e.g. <literal>org.varlink.service.InvalidParameter</literal>). Ensures that if + a method call fails with the specified error this will be treated as success, i.e. will cause the + <command>varlinkctl</command> invocation to exit with a zero exit status. This option may be used more + than once in order to treat multiple different errors as successes.</para> + + <xi:include href="version-info.xml" xpointer="v257"/> + </listitem> + </varlistentry> + <xi:include href="standard-options.xml" xpointer="no-pager" /> <xi:include href="standard-options.xml" xpointer="help" /> <xi:include href="standard-options.xml" xpointer="version" /> @@ -339,6 +360,22 @@ method Extend( {}</programlisting> </example> + <example> + <title>Invoking a method remotely via SSH</title> + + <para>The following command acquires a report about the identity of a remote host + <literal>somehost</literal> from + <citerefentry><refentrytitle>systemd-hostnamed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + by connecting via SSH to the <constant>AF_UNIX</constant> socket the service listens on:</para> + + <programlisting># varlinkctl call ssh-unix:somehost:/run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}'</programlisting> + + <para>To invoke a Varlink service binary directly on the remote host, rather than talking to a service + via <constant>AF_UNIX</constant> can be done like this:</para> + + <programlisting># varlinkctl call ssh-exec:somehost:/usr/bin/systemd-creds org.varlink.service.GetInfo '{}'</programlisting> + </example> + </refsect1> <refsect1> diff --git a/meson.build b/meson.build index dd335c1244..7f3ebc9fcb 100644 --- a/meson.build +++ b/meson.build @@ -547,6 +547,7 @@ decl_headers = ''' #include <uchar.h> #include <sys/mount.h> #include <sys/stat.h> +#include <sched.h> ''' foreach decl : ['char16_t', @@ -554,6 +555,7 @@ foreach decl : ['char16_t', 'struct mount_attr', 'struct statx', 'struct dirent64', + 'struct sched_attr', ] # We get -1 if the size cannot be determined @@ -601,6 +603,7 @@ foreach ident : [ #include <unistd.h>'''], # no known header declares pivot_root ['ioprio_get', '''#include <sched.h>'''], # no known header declares ioprio_get ['ioprio_set', '''#include <sched.h>'''], # no known header declares ioprio_set + ['sched_setattr', '''#include <sched.h>'''], # no known header declares sched_setattr ['name_to_handle_at', '''#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>'''], @@ -1740,6 +1743,7 @@ if conf.get('BPF_FRAMEWORK') == 1 '-ffile-prefix-map=', '-fdebug-prefix-map=', '-fmacro-prefix-map=', + '--sysroot=', ] foreach opt : c_args @@ -2651,12 +2655,6 @@ foreach executable : ['systemd-journal-remote', 'systemd-measure'] endforeach if mkosi.found() - genkey = custom_target('genkey', - output : ['mkosi.key', 'mkosi.crt'], - command : [mkosi, '--force', 'genkey'], - depends : mkosi_depends, - ) - custom_target('mkosi', build_always_stale : true, build_by_default: false, @@ -2668,14 +2666,10 @@ if mkosi.found() '--output-dir', meson.current_build_dir() / 'mkosi.output', '--cache-dir', meson.current_build_dir() / 'mkosi.cache', '--build-dir', meson.current_build_dir() / 'mkosi.builddir', - '--secure-boot-key', meson.current_build_dir() / 'mkosi.key', - '--secure-boot-certificate', meson.current_build_dir() / 'mkosi.crt', - '--verity-key', meson.current_build_dir() / 'mkosi.key', - '--verity-certificate', meson.current_build_dir() / 'mkosi.crt', '--force', 'build', ], - depends : mkosi_depends + [genkey], + depends : mkosi_depends, ) endif diff --git a/mkosi.conf.d/10-centos.conf b/mkosi.conf.d/10-centos.conf index ae2706c791..ee8d0e5581 100644 --- a/mkosi.conf.d/10-centos.conf +++ b/mkosi.conf.d/10-centos.conf @@ -8,3 +8,4 @@ Distribution=centos Repositories=epel epel-next hyperscale-packages-main + hyperscale-packages-experimental diff --git a/mkosi.images/exitrd/mkosi.conf.d/10-debian.conf b/mkosi.images/exitrd/mkosi.conf.d/10-debian.conf new file mode 100644 index 0000000000..68b0aa5fe7 --- /dev/null +++ b/mkosi.images/exitrd/mkosi.conf.d/10-debian.conf @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +[Match] +Distribution=debian + +[Content] +Packages= + systemd-standalone-shutdown diff --git a/mkosi.images/exitrd/mkosi.conf.d/10-debian-ubuntu.conf b/mkosi.images/exitrd/mkosi.conf.d/10-ubuntu.conf index babde60c9f..ddd68dc1b4 100644 --- a/mkosi.images/exitrd/mkosi.conf.d/10-debian-ubuntu.conf +++ b/mkosi.images/exitrd/mkosi.conf.d/10-ubuntu.conf @@ -1,8 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later [Match] -Distribution=|debian -Distribution=|ubuntu +Distribution=ubuntu [Content] Packages= diff --git a/mkosi.images/system/mkosi.conf b/mkosi.images/system/mkosi.conf index 562650a8ff..d1ab822132 100644 --- a/mkosi.images/system/mkosi.conf +++ b/mkosi.images/system/mkosi.conf @@ -17,6 +17,7 @@ ExtraTrees= PostInstallationScripts=mkosi.sanitizers.chroot InitrdPackages= + btrfs-progs findutils grep sed @@ -26,6 +27,7 @@ Packages= attr bash-completion bpftrace + btrfs-progs clang coreutils curl diff --git a/mkosi.images/system/mkosi.conf.d/10-arch/mkosi.conf b/mkosi.images/system/mkosi.conf.d/10-arch/mkosi.conf index 036b0a39a6..7072d08afa 100644 --- a/mkosi.images/system/mkosi.conf.d/10-arch/mkosi.conf +++ b/mkosi.images/system/mkosi.conf.d/10-arch/mkosi.conf @@ -20,7 +20,6 @@ VolatilePackages= Packages= bind bpf - btrfs-progs compiler-rt compsize cryptsetup @@ -61,7 +60,6 @@ Packages= vim InitrdPackages= - btrfs-progs compiler-rt tpm2-tools diff --git a/mkosi.images/system/mkosi.conf.d/10-debian-ubuntu/mkosi.conf b/mkosi.images/system/mkosi.conf.d/10-debian-ubuntu/mkosi.conf index ecac78049d..c1f74b1110 100644 --- a/mkosi.images/system/mkosi.conf.d/10-debian-ubuntu/mkosi.conf +++ b/mkosi.images/system/mkosi.conf.d/10-debian-ubuntu/mkosi.conf @@ -9,7 +9,7 @@ Environment= GIT_URL=https://salsa.debian.org/systemd-team/systemd.git GIT_SUBDIR=debian GIT_BRANCH=debian/master - GIT_COMMIT=596a70511736d78c1d8a5a27dca3989806cfa733 + GIT_COMMIT=3b4368d4b881122e39e1d236ba339dd3a6e306c4 VolatilePackages= libnss-myhostname @@ -22,10 +22,12 @@ VolatilePackages= systemd systemd-container systemd-coredump + systemd-cryptsetup systemd-dev systemd-homed systemd-journal-remote systemd-oomd + systemd-repart systemd-resolved systemd-sysv systemd-tests @@ -41,7 +43,6 @@ Packages= ^libubsan[0-9]+$ apt bind9-dnsutils - btrfs-progs cryptsetup-bin dbus-broker dbus-user-session @@ -81,10 +82,11 @@ Packages= xxd InitrdPackages= - btrfs-progs libclang-rt-dev tpm2-tools InitrdVolatilePackages= systemd + systemd-cryptsetup + systemd-repart udev diff --git a/mkosi.images/system/mkosi.conf.d/10-debian-ubuntu/mkosi.conf.d/10-debug.conf b/mkosi.images/system/mkosi.conf.d/10-debian-ubuntu/mkosi.conf.d/10-debug.conf index b53b3dcffe..2bb6164aa4 100644 --- a/mkosi.images/system/mkosi.conf.d/10-debian-ubuntu/mkosi.conf.d/10-debug.conf +++ b/mkosi.images/system/mkosi.conf.d/10-debian-ubuntu/mkosi.conf.d/10-debug.conf @@ -16,10 +16,12 @@ VolatilePackages= systemd-boot-dbgsym systemd-container-dbgsym systemd-coredump-dbgsym + systemd-cryptsetup-dbgsym systemd-dbgsym systemd-homed-dbgsym systemd-journal-remote-dbgsym systemd-oomd-dbgsym + systemd-repart-dbgsym systemd-resolved-dbgsym systemd-tests-dbgsym systemd-timesyncd-dbgsym diff --git a/mkosi.images/system/mkosi.conf.d/10-fedora/mkosi.conf b/mkosi.images/system/mkosi.conf.d/10-fedora/mkosi.conf index 689fe7d5c7..5e39902461 100644 --- a/mkosi.images/system/mkosi.conf.d/10-fedora/mkosi.conf +++ b/mkosi.images/system/mkosi.conf.d/10-fedora/mkosi.conf @@ -7,10 +7,9 @@ Distribution=fedora Environment= GIT_URL=https://src.fedoraproject.org/rpms/systemd.git GIT_BRANCH=rawhide - GIT_COMMIT=1f94b56cee818068f57debfd78f035edd29f0e61 + GIT_COMMIT=8153d9b0f978d633c8422011d4c547ae1f0e51a4 Packages= - btrfs-progs compsize dnf5 f2fs-tools @@ -18,6 +17,3 @@ Packages= # Required for systemd-networkd-tests.py (netdevsim and sch_xxx modules) kernel-modules-extra kernel-modules-internal - -InitrdPackages= - btrfs-progs diff --git a/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.build.chroot b/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.build.chroot index 3d6cc58b64..dbf3f2fa83 100755 --- a/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.build.chroot +++ b/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.build.chroot @@ -67,6 +67,9 @@ if ((WIPE)); then MKOSI_MESON_OPTIONS="$MKOSI_MESON_OPTIONS --wipe" fi +# TODO: Drop when the spec is fixed (either the patch is adapted or not applied when building for upstream). +sed --in-place '/0009-pid1-handle-console-specificities-weirdness-for-s390.patch/d' "pkg/$ID/systemd.spec" + build() { IFS= # shellcheck disable=SC2046 diff --git a/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.conf b/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.conf index 38ae0524a1..8c3a3d3cda 100644 --- a/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.conf +++ b/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.conf @@ -9,8 +9,8 @@ InitrdInclude=initrd/ [Content] Environment= GIT_URL=https://src.opensuse.org/rpm/systemd - GIT_BRANCH=factory - GIT_COMMIT=973534fe1a0a5746ead5bbb6dff8b9ccb9e010982997ed56eba8e44a41c5895d + GIT_BRANCH=devel + GIT_COMMIT=23bfa9d83b6e24a5395a704b816a351f3dc5b5316e580cacedd1b5d9e068c117 VolatilePackages= systemd @@ -32,7 +32,6 @@ VolatilePackages= Packages= bind-utils bpftool - btrfs-progs cryptsetup device-mapper dhcp-server @@ -88,7 +87,6 @@ Packages= xz InitrdPackages= - btrfs-progs clang kmod libkmod2 diff --git a/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.conf.d/10-debug.conf b/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.conf.d/10-debug.conf index 2262eae77e..6c57d04f65 100644 --- a/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.conf.d/10-debug.conf +++ b/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.conf.d/10-debug.conf @@ -9,7 +9,6 @@ VolatilePackages= libudev1-debuginfo systemd-boot-debuginfo systemd-container-debuginfo - systemd-coredump-debuginfo systemd-debuginfo systemd-debugsource systemd-experimental-debuginfo diff --git a/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.prepare b/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.prepare index 282a360bca..c57aa878b8 100755 --- a/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.prepare +++ b/mkosi.images/system/mkosi.conf.d/10-opensuse/mkosi.prepare @@ -15,6 +15,9 @@ if [ ! -f "pkg/$ID/systemd.spec" ]; then exit 1 fi +# TODO: Drop when the spec is fixed (either the patch is adapted or not applied when building for upstream). +sed --in-place '/0009-pid1-handle-console-specificities-weirdness-for-s390.patch/d' "pkg/$ID/systemd.spec" + for DEPS in --requires --buildrequires; do mkosi-chroot \ rpmspec \ diff --git a/mkosi.images/system/mkosi.repart/10-root.conf b/mkosi.images/system/mkosi.repart/10-root.conf index 715b925463..3c25dbfb14 100644 --- a/mkosi.images/system/mkosi.repart/10-root.conf +++ b/mkosi.images/system/mkosi.repart/10-root.conf @@ -2,7 +2,7 @@ [Partition] Type=root -Format=ext4 +Format=btrfs CopyFiles=/ SizeMinBytes=8G SizeMaxBytes=8G diff --git a/shell-completion/bash/systemd-cryptenroll b/shell-completion/bash/systemd-cryptenroll index 6b13e58789..7a11a3f3dc 100644 --- a/shell-completion/bash/systemd-cryptenroll +++ b/shell-completion/bash/systemd-cryptenroll @@ -57,6 +57,8 @@ _systemd_cryptenroll() { --pkcs11-token-uri --fido2-credential-algorithm --fido2-device + --fido2-salt-file + --fido2-parameters-in-header --fido2-with-client-pin --fido2-with-user-presence --fido2-with-user-verification @@ -76,7 +78,7 @@ _systemd_cryptenroll() { if __contains_word "$prev" ${OPTS[ARG]}; then case $prev in - --unlock-key-file|--tpm2-device-key|--tpm2-public-key|--tpm2-signature|--tpm2-pcrlock) + --unlock-key-file|--fido2-salt-file|--tpm2-device-key|--tpm2-public-key|--tpm2-signature|--tpm2-pcrlock) comps=$(compgen -A file -- "$cur") compopt -o filenames ;; @@ -95,7 +97,7 @@ _systemd_cryptenroll() { --fido2-device) comps="auto list $(__get_fido2_devices)" ;; - --fido2-with-client-pin|--fido2-with-user-presence|--fido2-with-user-verification|--tpm2-with-pin) + --fido2-parameters-in-header|--fido2-with-client-pin|--fido2-with-user-presence|--fido2-with-user-verification|--tpm2-with-pin) comps='yes no' ;; --tpm2-device) diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 6aa67a9339..9a247a085a 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -1244,8 +1244,8 @@ static const struct security_assessor security_assessor_table[] = { { .id = "CapabilityBoundingSet=~CAP_BPF", .json_field = "CapabilityBoundingSet_CAP_BPF", - .description_good = "Service may load BPF programs", - .description_bad = "Service may not load BPF programs", + .description_good = "Service may not load BPF programs", + .description_bad = "Service may load BPF programs", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=", .weight = 25, .range = 1, diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index a499fef9ec..b92257cb0d 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -6,6 +6,7 @@ #include <sys/uio.h> #include "alloc-util.h" +#include "iovec-util-fundamental.h" #include "macro.h" extern const struct iovec iovec_nul_byte; /* Points to a single NUL byte */ @@ -15,13 +16,6 @@ size_t iovec_total_size(const struct iovec *iovec, size_t n); bool iovec_increment(struct iovec *iovec, size_t n, size_t k); -/* This accepts both const and non-const pointers */ -#define IOVEC_MAKE(base, len) \ - (struct iovec) { \ - .iov_base = (void*) (base), \ - .iov_len = (len), \ - } - static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { assert(iovec); /* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */ @@ -38,14 +32,6 @@ static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s .iov_len = STRLEN(s), \ } -static inline void iovec_done(struct iovec *iovec) { - /* A _cleanup_() helper that frees the iov_base in the iovec */ - assert(iovec); - - iovec->iov_base = mfree(iovec->iov_base); - iovec->iov_len = 0; -} - static inline void iovec_done_erase(struct iovec *iovec) { assert(iovec); @@ -53,16 +39,6 @@ static inline void iovec_done_erase(struct iovec *iovec) { iovec->iov_len = 0; } -static inline bool iovec_is_set(const struct iovec *iovec) { - /* Checks if the iovec points to a non-empty chunk of memory */ - return iovec && iovec->iov_len > 0 && iovec->iov_base; -} - -static inline bool iovec_is_valid(const struct iovec *iovec) { - /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */ - return !iovec || (iovec->iov_base || iovec->iov_len == 0); -} - char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value); diff --git a/src/basic/macro.h b/src/basic/macro.h index 19d5039fd3..7e2bc628db 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -211,16 +211,10 @@ static inline int __coverity_check_and_return__(int condition) { #define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) #define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) -#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) -#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) - #define CHAR_TO_STR(x) ((char[2]) { x, 0 }) #define char_array_0(x) x[sizeof(x)-1] = 0; -#define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member) -#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member)) - /* Maximum buffer size needed for formatting an unsigned integer type as hex, including space for '0x' * prefix and trailing NUL suffix. */ #define HEXADECIMAL_STR_MAX(type) (2 + sizeof(type) * 2 + 1) @@ -266,18 +260,6 @@ static inline int __coverity_check_and_return__(int condition) { /* Pointers range from NULL to POINTER_MAX */ #define POINTER_MAX ((void*) UINTPTR_MAX) -#define _FOREACH_ARRAY(i, array, num, m, end) \ - for (typeof(array[0]) *i = (array), *end = ({ \ - typeof(num) m = (num); \ - (i && m > 0) ? i + m : NULL; \ - }); end && i < end; i++) - -#define FOREACH_ARRAY(i, array, num) \ - _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) - -#define FOREACH_ELEMENT(i, array) \ - FOREACH_ARRAY(i, array, ELEMENTSOF(array)) - #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ scope type *name##_ref(type *p) { \ if (!p) \ diff --git a/src/basic/missing_sched.h b/src/basic/missing_sched.h index b8109d30ac..bd83b41a71 100644 --- a/src/basic/missing_sched.h +++ b/src/basic/missing_sched.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include <linux/types.h> #include <sched.h> #include "macro.h" @@ -35,3 +36,20 @@ assert_cc(PF_KTHREAD == 0x00200000); #else assert_cc(TASK_COMM_LEN == 16); #endif + +#if !HAVE_STRUCT_SCHED_ATTR +struct sched_attr { + __u32 size; /* Size of this structure */ + __u32 sched_policy; /* Policy (SCHED_*) */ + __u64 sched_flags; /* Flags */ + __s32 sched_nice; /* Nice value (SCHED_OTHER, + SCHED_BATCH) */ + __u32 sched_priority; /* Static priority (SCHED_FIFO, + SCHED_RR) */ + /* Remaining fields are for SCHED_DEADLINE + and potentially soon for SCHED_OTHER/SCHED_BATCH */ + __u64 sched_runtime; + __u64 sched_deadline; + __u64 sched_period; +}; +#endif diff --git a/src/basic/missing_syscall.h b/src/basic/missing_syscall.h index 86280771c4..e2cd8b4e35 100644 --- a/src/basic/missing_syscall.h +++ b/src/basic/missing_syscall.h @@ -22,6 +22,7 @@ #include "macro.h" #include "missing_keyctl.h" +#include "missing_sched.h" #include "missing_stat.h" #include "missing_syscall_def.h" @@ -667,6 +668,22 @@ static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) { /* ======================================================================= */ +#if !HAVE_SCHED_SETATTR + +static inline ssize_t missing_sched_setattr(pid_t pid, struct sched_attr *attr, unsigned int flags) { +# if defined __NR_sched_setattr + return syscall(__NR_sched_setattr, pid, attr, flags); +# else + errno = ENOSYS; + return -1; +# endif +} + +# define sched_setattr missing_sched_setattr +#endif + +/* ======================================================================= */ + /* glibc does not provide clone() on ia64, only clone2(). Not only that, but it also doesn't provide a * prototype, only the symbol in the shared library (it provides a prototype for clone(), but not the * symbol in the shared library). */ diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 787ae7abac..cf27134984 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -40,6 +40,7 @@ #include "socket-util.h" #include "stat-util.h" #include "stdio-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" @@ -51,7 +52,7 @@ static volatile unsigned cached_lines = 0; static volatile int cached_on_tty = -1; static volatile int cached_on_dev_null = -1; -static volatile int cached_color_mode = _COLOR_INVALID; +static volatile int cached_color_mode = _COLOR_MODE_INVALID; static volatile int cached_underline_enabled = -1; bool isatty_safe(int fd) { @@ -959,7 +960,7 @@ void reset_terminal_feature_caches(void) { cached_columns = 0; cached_lines = 0; - cached_color_mode = _COLOR_INVALID; + cached_color_mode = _COLOR_MODE_INVALID; cached_underline_enabled = -1; cached_on_tty = -1; cached_on_dev_null = -1; @@ -1315,69 +1316,68 @@ bool terminal_is_dumb(void) { return getenv_terminal_is_dumb(); } +static const char* const color_mode_table[_COLOR_MODE_MAX] = { + [COLOR_OFF] = "off", + [COLOR_16] = "16", + [COLOR_256] = "256", + [COLOR_24BIT] = "24bit", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(color_mode, ColorMode, COLOR_24BIT); + static ColorMode parse_systemd_colors(void) { const char *e; - int r; e = getenv("SYSTEMD_COLORS"); if (!e) - return _COLOR_INVALID; - if (streq(e, "16")) - return COLOR_16; - if (streq(e, "256")) - return COLOR_256; - r = parse_boolean(e); - if (r >= 0) - return r > 0 ? COLOR_ON : COLOR_OFF; - return _COLOR_INVALID; -} + return _COLOR_MODE_INVALID; -ColorMode get_color_mode(void) { + ColorMode m = color_mode_from_string(e); + if (m < 0) + return log_debug_errno(m, "Failed to parse $SYSTEMD_COLORS value '%s', ignoring: %m", e); + return m; +} + +static ColorMode get_color_mode_impl(void) { /* Returns the mode used to choose output colors. The possible modes are COLOR_OFF for no colors, - * COLOR_16 for only the base 16 ANSI colors, COLOR_256 for more colors and COLOR_ON for unrestricted - * color output. For that we check $SYSTEMD_COLORS first (which is the explicit way to - * change the mode). If that didn't work we turn colors off unless we are on a TTY. And if we are on a TTY - * we turn it off if $TERM is set to "dumb". There's one special tweak though: if we are PID 1 then we do not - * check whether we are connected to a TTY, because we don't keep /dev/console open continuously due to fear - * of SAK, and hence things are a bit weird. */ - ColorMode m; + * COLOR_16 for only the base 16 ANSI colors, COLOR_256 for more colors, and COLOR_24BIT for + * unrestricted color output. */ - if (cached_color_mode < 0) { - m = parse_systemd_colors(); - if (m >= 0) - cached_color_mode = m; - else if (getenv("NO_COLOR")) - /* We only check for the presence of the variable; value is ignored. */ - cached_color_mode = COLOR_OFF; - - else if (getpid_cached() == 1) { - /* PID1 outputs to the console without holding it open all the time. - * - * Note that the Linux console can only display 16 colors. We still enable 256 color - * mode even for PID1 output though (which typically goes to the Linux console), - * since the Linux console is able to parse the 256 color sequences and automatically - * map them to the closest color in the 16 color palette (since kernel 3.16). Doing - * 256 colors is nice for people who invoke systemd in a container or via a serial - * link or such, and use a true 256 color terminal to do so. */ - if (getenv_terminal_is_dumb()) - cached_color_mode = COLOR_OFF; - } else { - if (terminal_is_dumb()) - cached_color_mode = COLOR_OFF; - } + /* First, we check $SYSTEMD_COLORS, which is the explicit way to change the mode. */ + ColorMode m = parse_systemd_colors(); + if (m >= 0) + return m; - if (cached_color_mode < 0) { - /* We failed to figure out any reason to *disable* colors. - * Let's see how many colors we shall use. */ - if (STRPTR_IN_SET(getenv("COLORTERM"), - "truecolor", - "24bit")) - cached_color_mode = COLOR_24BIT; - else - cached_color_mode = COLOR_256; - } - } + /* Next, check for the presence of $NO_COLOR; value is ignored. */ + if (getenv("NO_COLOR")) + return COLOR_OFF; + + /* If the above didn't work, we turn colors off unless we are on a TTY. And if we are on a TTY we + * turn it off if $TERM is set to "dumb". There's one special tweak though: if we are PID 1 then we + * do not check whether we are connected to a TTY, because we don't keep /dev/console open + * continuously due to fear of SAK, and hence things are a bit weird. */ + if (getpid_cached() == 1 ? getenv_terminal_is_dumb() : terminal_is_dumb()) + return COLOR_OFF; + + /* We failed to figure out any reason to *disable* colors. Let's see how many colors we shall use. */ + if (STRPTR_IN_SET(getenv("COLORTERM"), + "truecolor", + "24bit")) + return COLOR_24BIT; + + /* Note that the Linux console can only display 16 colors. We still enable 256 color mode + * even for PID1 output though (which typically goes to the Linux console), since the Linux + * console is able to parse the 256 color sequences and automatically map them to the closest + * color in the 16 color palette (since kernel 3.16). Doing 256 colors is nice for people who + * invoke systemd in a container or via a serial link or such, and use a true 256 color + * terminal to do so. */ + return COLOR_256; +} + +ColorMode get_color_mode(void) { + if (cached_color_mode < 0) + cached_color_mode = get_color_mode_impl(); return cached_color_mode; } diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 0c1c6373bc..691b37aa51 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -42,26 +42,26 @@ #define ANSI_HIGHLIGHT_MAGENTA "\x1B[0;1;35m" #define ANSI_HIGHLIGHT_CYAN "\x1B[0;1;36m" #define ANSI_HIGHLIGHT_WHITE "\x1B[0;1;37m" -#define ANSI_HIGHLIGHT_YELLOW4 "\x1B[0;1;38;5;100m" -#define ANSI_HIGHLIGHT_KHAKI3 "\x1B[0;1;38;5;185m" -#define ANSI_HIGHLIGHT_GREY "\x1B[0;1;38;5;245m" +#define ANSI_HIGHLIGHT_YELLOW4 "\x1B[0;1;38:5:100m" +#define ANSI_HIGHLIGHT_KHAKI3 "\x1B[0;1;38:5:185m" +#define ANSI_HIGHLIGHT_GREY "\x1B[0;1;38:5:245m" #define ANSI_HIGHLIGHT_YELLOW ANSI_HIGHLIGHT_KHAKI3 /* Replacement yellow that is more legible */ /* Underlined */ -#define ANSI_GREY_UNDERLINE "\x1B[0;4;38;5;245m" +#define ANSI_GREY_UNDERLINE "\x1B[0;4;38:5:245m" #define ANSI_BRIGHT_BLACK_UNDERLINE "\x1B[0;4;90m" #define ANSI_HIGHLIGHT_RED_UNDERLINE "\x1B[0;1;4;31m" #define ANSI_HIGHLIGHT_GREEN_UNDERLINE "\x1B[0;1;4;32m" -#define ANSI_HIGHLIGHT_YELLOW_UNDERLINE "\x1B[0;1;4;38;5;185m" +#define ANSI_HIGHLIGHT_YELLOW_UNDERLINE "\x1B[0;1;4;38:5:185m" #define ANSI_HIGHLIGHT_BLUE_UNDERLINE "\x1B[0;1;4;34m" #define ANSI_HIGHLIGHT_MAGENTA_UNDERLINE "\x1B[0;1;4;35m" -#define ANSI_HIGHLIGHT_GREY_UNDERLINE "\x1B[0;1;4;38;5;245m" +#define ANSI_HIGHLIGHT_GREY_UNDERLINE "\x1B[0;1;4;38:5:245m" /* Other ANSI codes */ #define ANSI_UNDERLINE "\x1B[0;4m" #define ANSI_ADD_UNDERLINE "\x1B[4m" -#define ANSI_ADD_UNDERLINE_GREY ANSI_ADD_UNDERLINE "\x1B[58;5;245m" +#define ANSI_ADD_UNDERLINE_GREY ANSI_ADD_UNDERLINE "\x1B[58:5:245m" #define ANSI_HIGHLIGHT "\x1B[0;1;39m" #define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" @@ -119,24 +119,17 @@ typedef enum AcquireTerminalFlags { /* Limits the use of ANSI colors to a subset. */ typedef enum ColorMode { - /* No colors, monochrome output. */ - COLOR_OFF, - - /* All colors, no restrictions. */ - COLOR_ON, - - /* Only the base 16 colors. */ - COLOR_16, - - /* Only 256 colors. */ - COLOR_256, - - /* For truecolor or 24bit color support. */ - COLOR_24BIT, - - _COLOR_INVALID = -EINVAL, + COLOR_OFF, /* No colors, monochrome output. */ + COLOR_16, /* Only the base 16 colors. */ + COLOR_256, /* Only 256 colors. */ + COLOR_24BIT, /* For truecolor or 24bit color support, no restriction. */ + _COLOR_MODE_MAX, + _COLOR_MODE_INVALID = -EINVAL, } ColorMode; +const char* color_mode_to_string(ColorMode m) _const_; +ColorMode color_mode_from_string(const char *s) _pure_; + int acquire_terminal(const char *name, AcquireTerminalFlags flags, usec_t timeout); int release_terminal(void); diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 79de121f0d..9dbf96f169 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -7,6 +7,7 @@ #include "devicetree.h" #include "drivers.h" #include "efivars-fundamental.h" +#include "export-vars.h" #include "graphics.h" #include "initrd.h" #include "linux.h" @@ -1855,23 +1856,24 @@ static void generate_boot_entry_titles(Config *config) { } static bool is_sd_boot(EFI_FILE *root_dir, const char16_t *loader_path) { - EFI_STATUS err; static const char * const sections[] = { ".sdmagic", NULL }; - size_t offset = 0, size = 0, read; _cleanup_free_ char *content = NULL; + PeSectionVector vector = {}; + EFI_STATUS err; + size_t read; assert(root_dir); assert(loader_path); - err = pe_file_locate_sections(root_dir, loader_path, sections, &offset, &size); - if (err != EFI_SUCCESS || size != sizeof(SD_MAGIC)) + err = pe_file_locate_sections(root_dir, loader_path, sections, &vector); + if (err != EFI_SUCCESS || vector.size != sizeof(SD_MAGIC)) return false; - err = file_read(root_dir, loader_path, offset, size, &content, &read); - if (err != EFI_SUCCESS || size != read) + err = file_read(root_dir, loader_path, vector.file_offset, vector.size, &content, &read); + if (err != EFI_SUCCESS || vector.size != read) return false; return memcmp(content, SD_MAGIC, sizeof(SD_MAGIC)) == 0; @@ -2104,7 +2106,7 @@ static void config_load_type2_entries( _SECTION_MAX, }; - static const char * const sections[_SECTION_MAX + 1] = { + static const char * const section_names[_SECTION_MAX + 1] = { [SECTION_CMDLINE] = ".cmdline", [SECTION_OSREL] = ".osrel", NULL, @@ -2114,8 +2116,9 @@ static void config_load_type2_entries( *os_image_version = NULL, *os_version = NULL, *os_version_id = NULL, *os_build_id = NULL; const char16_t *good_name, *good_version, *good_sort_key; _cleanup_free_ char *content = NULL; - size_t offs[_SECTION_MAX] = {}, szs[_SECTION_MAX] = {}, pos = 0; + PeSectionVector sections[_SECTION_MAX] = {}; char *line, *key, *value; + size_t pos = 0; err = readdir(linux_dir, &f, &f_size); if (err != EFI_SUCCESS || !f) @@ -2131,11 +2134,16 @@ static void config_load_type2_entries( continue; /* look for .osrel and .cmdline sections in the .efi binary */ - err = pe_file_locate_sections(linux_dir, f->FileName, sections, offs, szs); - if (err != EFI_SUCCESS || szs[SECTION_OSREL] == 0) + err = pe_file_locate_sections(linux_dir, f->FileName, section_names, sections); + if (err != EFI_SUCCESS || !PE_SECTION_VECTOR_IS_SET(sections + SECTION_OSREL)) continue; - err = file_read(linux_dir, f->FileName, offs[SECTION_OSREL], szs[SECTION_OSREL], &content, NULL); + err = file_read(linux_dir, + f->FileName, + sections[SECTION_OSREL].file_offset, + sections[SECTION_OSREL].size, + &content, + NULL); if (err != EFI_SUCCESS) continue; @@ -2206,14 +2214,19 @@ static void config_load_type2_entries( config_add_entry(config, entry); boot_entry_parse_tries(entry, u"\\EFI\\Linux", f->FileName, u".efi"); - if (szs[SECTION_CMDLINE] == 0) + if (!PE_SECTION_VECTOR_IS_SET(sections + SECTION_CMDLINE)) continue; content = mfree(content); /* read the embedded cmdline file */ size_t cmdline_len; - err = file_read(linux_dir, f->FileName, offs[SECTION_CMDLINE], szs[SECTION_CMDLINE], &content, &cmdline_len); + err = file_read(linux_dir, + f->FileName, + sections[SECTION_CMDLINE].file_offset, + sections[SECTION_CMDLINE].size, + &content, + &cmdline_len); if (err == EFI_SUCCESS) { entry->options = xstrn8_to_16(content, cmdline_len); mangle_stub_cmdline(entry->options); @@ -2526,9 +2539,8 @@ static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) return EFI_SUCCESS; } -static void export_variables( +static void export_loader_variables( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, - const char16_t *loaded_image_path, uint64_t init_usec) { static const uint64_t loader_features = @@ -2548,28 +2560,11 @@ static void export_variables( EFI_LOADER_FEATURE_MENU_DISABLE | 0; - _cleanup_free_ char16_t *infostr = NULL, *typestr = NULL; - assert(loaded_image); - efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeInitUSec", init_usec); - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderInfo", u"systemd-boot " GIT_VERSION, 0); - - infostr = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", infostr, 0); - - typestr = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", typestr, 0); - + (void) efivar_set_time_usec(MAKE_GUID_PTR(LOADER), u"LoaderTimeInitUSec", init_usec); + (void) efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderInfo", u"systemd-boot " GIT_VERSION, 0); (void) efivar_set_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", loader_features, 0); - - /* the filesystem path to this image, to prevent adding ourselves to the menu */ - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", loaded_image_path, 0); - - /* export the device path this image is started from */ - _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle); - if (uuid) - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0); } static void config_load_all_entries( @@ -2669,7 +2664,6 @@ static EFI_STATUS run(EFI_HANDLE image) { EFI_LOADED_IMAGE_PROTOCOL *loaded_image; _cleanup_(file_closep) EFI_FILE *root_dir = NULL; _cleanup_(config_free) Config config = {}; - _cleanup_free_ char16_t *loaded_image_path = NULL; EFI_STATUS err; uint64_t init_usec; bool menu = false; @@ -2684,9 +2678,8 @@ static EFI_STATUS run(EFI_HANDLE image) { if (err != EFI_SUCCESS) return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m"); - (void) device_path_to_str(loaded_image->FilePath, &loaded_image_path); - - export_variables(loaded_image, loaded_image_path, init_usec); + export_common_variables(loaded_image); + export_loader_variables(loaded_image, init_usec); err = discover_root_dir(loaded_image, &root_dir); if (err != EFI_SUCCESS) @@ -2694,6 +2687,8 @@ static EFI_STATUS run(EFI_HANDLE image) { (void) load_drivers(image, loaded_image, root_dir); + _cleanup_free_ char16_t *loaded_image_path = NULL; + (void) device_path_to_str(loaded_image->FilePath, &loaded_image_path); config_load_all_entries(&config, loaded_image, loaded_image_path, root_dir); if (config.n_entries == 0) diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index bd1118a58a..ba439740f7 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -311,8 +311,7 @@ EFI_STATUS pack_cpio( uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, - void **ret_buffer, - size_t *ret_buffer_size, + struct iovec *ret_buffer, bool *ret_measured) { _cleanup_(file_closep) EFI_FILE *root = NULL, *extra_dir = NULL; @@ -327,7 +326,6 @@ EFI_STATUS pack_cpio( assert(loaded_image); assert(target_dir_prefix); assert(ret_buffer); - assert(ret_buffer_size); if (!loaded_image->DeviceHandle) goto nothing; @@ -430,7 +428,7 @@ EFI_STATUS pack_cpio( if (err != EFI_SUCCESS) return log_error_status(err, "Failed to pack cpio trailer: %m"); - err = tpm_log_event( + err = tpm_log_ipl_event( tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured); if (err != EFI_SUCCESS) return log_error_status( @@ -439,14 +437,11 @@ EFI_STATUS pack_cpio( tpm_pcr, tpm_description); - *ret_buffer = TAKE_PTR(buffer); - *ret_buffer_size = buffer_size; - + *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); return EFI_SUCCESS; nothing: - *ret_buffer = NULL; - *ret_buffer_size = 0; + *ret_buffer = (struct iovec) {}; if (ret_measured) *ret_measured = false; @@ -463,8 +458,7 @@ EFI_STATUS pack_cpio_literal( uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, - void **ret_buffer, - size_t *ret_buffer_size, + struct iovec *ret_buffer, bool *ret_measured) { uint32_t inode = 1; /* inode counter, so that each item gets a new inode */ @@ -476,7 +470,6 @@ EFI_STATUS pack_cpio_literal( assert(target_dir_prefix); assert(target_filename); assert(ret_buffer); - assert(ret_buffer_size); /* Generate the leading directory inodes right before adding the first files, to the * archive. Otherwise the cpio archive cannot be unpacked, since the leading dirs won't exist. */ @@ -499,7 +492,7 @@ EFI_STATUS pack_cpio_literal( if (err != EFI_SUCCESS) return log_error_status(err, "Failed to pack cpio trailer: %m"); - err = tpm_log_event( + err = tpm_log_ipl_event( tpm_pcr, POINTER_TO_PHYSICAL_ADDRESS(buffer), buffer_size, tpm_description, ret_measured); if (err != EFI_SUCCESS) return log_error_status( @@ -508,8 +501,6 @@ EFI_STATUS pack_cpio_literal( tpm_pcr, tpm_description); - *ret_buffer = TAKE_PTR(buffer); - *ret_buffer_size = buffer_size; - + *ret_buffer = IOVEC_MAKE(TAKE_PTR(buffer), buffer_size); return EFI_SUCCESS; } diff --git a/src/boot/efi/cpio.h b/src/boot/efi/cpio.h index 9d14fa15a2..660372c0b7 100644 --- a/src/boot/efi/cpio.h +++ b/src/boot/efi/cpio.h @@ -2,6 +2,7 @@ #pragma once #include "efi.h" +#include "iovec-util-fundamental.h" #include "proto/loaded-image.h" EFI_STATUS pack_cpio( @@ -14,8 +15,7 @@ EFI_STATUS pack_cpio( uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, - void **ret_buffer, - size_t *ret_buffer_size, + struct iovec *ret_buffer, bool *ret_measured); EFI_STATUS pack_cpio_literal( @@ -27,6 +27,5 @@ EFI_STATUS pack_cpio_literal( uint32_t access_mode, uint32_t tpm_pcr, const char16_t *tpm_description, - void **ret_buffer, - size_t *ret_buffer_size, + struct iovec *ret_buffer, bool *ret_measured); diff --git a/src/boot/efi/export-vars.c b/src/boot/efi/export-vars.c new file mode 100644 index 0000000000..3926747bba --- /dev/null +++ b/src/boot/efi/export-vars.c @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "device-path-util.h" +#include "export-vars.h" +#include "part-discovery.h" +#include "util.h" + +void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { + assert(loaded_image); + + /* Export the device path this image is started from, if it's not set yet */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle); + if (uuid) + efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0); + } + + /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the + * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note + * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us, + * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong + * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath + * is non-NULL explicitly.) */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS && + loaded_image->FilePath) { + _cleanup_free_ char16_t *s = NULL; + if (device_path_to_str(loaded_image->FilePath, &s) == EFI_SUCCESS) + efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", s, 0); + } + + /* if LoaderFirmwareInfo is not set, let's set it */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *s = NULL; + s = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); + efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", s, 0); + } + + /* ditto for LoaderFirmwareType */ + if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) { + _cleanup_free_ char16_t *s = NULL; + s = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); + efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", s, 0); + } +} diff --git a/src/boot/efi/export-vars.h b/src/boot/efi/export-vars.h new file mode 100644 index 0000000000..a925d09827 --- /dev/null +++ b/src/boot/efi/export-vars.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "proto/loaded-image.h" + +void export_common_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image); diff --git a/src/boot/efi/linux.c b/src/boot/efi/linux.c index 65bc176df7..706648b49d 100644 --- a/src/boot/efi/linux.c +++ b/src/boot/efi/linux.c @@ -93,19 +93,17 @@ static EFI_STATUS load_image(EFI_HANDLE parent, const void *source, size_t len, EFI_STATUS linux_exec( EFI_HANDLE parent, const char16_t *cmdline, - const void *linux_buffer, - size_t linux_length, - const void *initrd_buffer, - size_t initrd_length) { + const struct iovec *kernel, + const struct iovec *initrd) { uint32_t compat_address; EFI_STATUS err; assert(parent); - assert(linux_buffer && linux_length > 0); - assert(initrd_buffer || initrd_length == 0); + assert(iovec_is_set(kernel)); + assert(iovec_is_valid(initrd)); - err = pe_kernel_info(linux_buffer, &compat_address); + err = pe_kernel_info(kernel->iov_base, &compat_address); #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 @@ -113,16 +111,14 @@ EFI_STATUS linux_exec( return linux_exec_efi_handover( parent, cmdline, - linux_buffer, - linux_length, - initrd_buffer, - initrd_length); + kernel, + initrd); #endif if (err != EFI_SUCCESS) return log_error_status(err, "Bad kernel image: %m"); _cleanup_(unload_imagep) EFI_HANDLE kernel_image = NULL; - err = load_image(parent, linux_buffer, linux_length, &kernel_image); + err = load_image(parent, kernel->iov_base, kernel->iov_len, &kernel_image); if (err != EFI_SUCCESS) return log_error_status(err, "Error loading kernel image: %m"); @@ -138,7 +134,7 @@ EFI_STATUS linux_exec( } _cleanup_(cleanup_initrd) EFI_HANDLE initrd_handle = NULL; - err = initrd_register(initrd_buffer, initrd_length, &initrd_handle); + err = initrd_register(initrd->iov_base, initrd->iov_len, &initrd_handle); if (err != EFI_SUCCESS) return log_error_status(err, "Error registering initrd: %m"); diff --git a/src/boot/efi/linux.h b/src/boot/efi/linux.h index 46b5f4f4d7..1bee8907c4 100644 --- a/src/boot/efi/linux.h +++ b/src/boot/efi/linux.h @@ -2,18 +2,15 @@ #pragma once #include "efi.h" +#include "iovec-util-fundamental.h" EFI_STATUS linux_exec( EFI_HANDLE parent, const char16_t *cmdline, - const void *linux_buffer, - size_t linux_length, - const void *initrd_buffer, - size_t initrd_length); + const struct iovec *kernel, + const struct iovec *initrd); EFI_STATUS linux_exec_efi_handover( EFI_HANDLE parent, const char16_t *cmdline, - const void *linux_buffer, - size_t linux_length, - const void *initrd_buffer, - size_t initrd_length); + const struct iovec *kernel, + const struct iovec *initrd); diff --git a/src/boot/efi/linux_x86.c b/src/boot/efi/linux_x86.c index 757902daac..c456209831 100644 --- a/src/boot/efi/linux_x86.c +++ b/src/boot/efi/linux_x86.c @@ -123,19 +123,17 @@ static void linux_efi_handover(EFI_HANDLE parent, uintptr_t kernel, BootParams * EFI_STATUS linux_exec_efi_handover( EFI_HANDLE parent, const char16_t *cmdline, - const void *linux_buffer, - size_t linux_length, - const void *initrd_buffer, - size_t initrd_length) { + const struct iovec *kernel, + const struct iovec *initrd) { assert(parent); - assert(linux_buffer); - assert(initrd_buffer || initrd_length == 0); + assert(iovec_is_set(kernel)); + assert(iovec_is_valid(initrd)); - if (linux_length < sizeof(BootParams)) + if (kernel->iov_len < sizeof(BootParams)) return EFI_LOAD_ERROR; - const BootParams *image_params = (const BootParams *) linux_buffer; + const BootParams *image_params = (const BootParams *) kernel->iov_base; if (image_params->hdr.header != SETUP_MAGIC || image_params->hdr.boot_flag != BOOT_FLAG_MAGIC) return log_error_status(EFI_UNSUPPORTED, "Unsupported kernel image."); if (image_params->hdr.version < SETUP_VERSION_2_11) @@ -155,22 +153,26 @@ EFI_STATUS linux_exec_efi_handover( /* There is no way to pass the high bits of code32_start. Newer kernels seems to handle this * just fine, but older kernels will fail even if they otherwise have above 4G boot support. */ _cleanup_pages_ Pages linux_relocated = {}; - if (POINTER_TO_PHYSICAL_ADDRESS(linux_buffer) + linux_length > UINT32_MAX) { + const void *linux_buffer; + if (POINTER_TO_PHYSICAL_ADDRESS(kernel->iov_base) + kernel->iov_len > UINT32_MAX) { linux_relocated = xmalloc_pages( - AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(linux_length), UINT32_MAX); + AllocateMaxAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES(kernel->iov_len), UINT32_MAX); linux_buffer = memcpy( - PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), linux_buffer, linux_length); - } + PHYSICAL_ADDRESS_TO_POINTER(linux_relocated.addr), kernel->iov_base, kernel->iov_len); + } else + linux_buffer = kernel->iov_base; _cleanup_pages_ Pages initrd_relocated = {}; - if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) + initrd_length > UINT32_MAX) { + const void *initrd_buffer; + if (!can_4g && POINTER_TO_PHYSICAL_ADDRESS(initrd->iov_base) + initrd->iov_len > UINT32_MAX) { initrd_relocated = xmalloc_pages( - AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(initrd_length), UINT32_MAX); + AllocateMaxAddress, EfiLoaderData, EFI_SIZE_TO_PAGES(initrd->iov_len), UINT32_MAX); initrd_buffer = memcpy( PHYSICAL_ADDRESS_TO_POINTER(initrd_relocated.addr), - initrd_buffer, - initrd_length); - } + initrd->iov_base, + initrd->iov_len); + } else + initrd_buffer = initrd->iov_base; _cleanup_pages_ Pages boot_params_page = xmalloc_pages( can_4g ? AllocateAnyPages : AllocateMaxAddress, @@ -215,8 +217,8 @@ EFI_STATUS linux_exec_efi_handover( boot_params->hdr.ramdisk_image = (uintptr_t) initrd_buffer; boot_params->ext_ramdisk_image = POINTER_TO_PHYSICAL_ADDRESS(initrd_buffer) >> 32; - boot_params->hdr.ramdisk_size = initrd_length; - boot_params->ext_ramdisk_size = ((uint64_t) initrd_length) >> 32; + boot_params->hdr.ramdisk_size = initrd->iov_len; + boot_params->ext_ramdisk_size = ((uint64_t) initrd->iov_len) >> 32; log_wait(); linux_efi_handover(parent, (uintptr_t) linux_buffer, boot_params); diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c index 08a2ecdba8..15f1ba9aff 100644 --- a/src/boot/efi/measure.c +++ b/src/boot/efi/measure.c @@ -27,6 +27,8 @@ static EFI_STATUS tpm2_measure_to_pcr_and_tagged_event_log( assert(tcg); assert(description); + /* New style stuff we log as EV_EVENT_TAG with a recognizable event tag. */ + desc_len = strsize16(description); event_size = offsetof(EFI_TCG2_EVENT, Event) + offsetof(EFI_TCG2_TAGGED_EVENT, Event) + desc_len; @@ -53,7 +55,7 @@ static EFI_STATUS tpm2_measure_to_pcr_and_tagged_event_log( &event->tcg_event); } -static EFI_STATUS tpm2_measure_to_pcr_and_event_log( +static EFI_STATUS tpm2_measure_to_pcr_and_ipl_event_log( EFI_TCG2_PROTOCOL *tcg, uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, @@ -66,11 +68,10 @@ static EFI_STATUS tpm2_measure_to_pcr_and_event_log( assert(tcg); assert(description); - /* NB: We currently record everything as EV_IPL. Which sucks, because it makes it hard to - * recognize from the event log which of the events are ours. Measurement logs are kinda API hence - * this is hard to change for existing, established events. But for future additions, let's use - * EV_EVENT_TAG instead, with a tag of our choosing that makes clear what precisely we are measuring - * here. */ + /* We record older stuff as EV_IPL. Which sucks, because it makes it hard to recognize from the event + * log which of the events are ours. Measurement logs are kinda API hence this is hard to change for + * existing, established events. But for future additions, let's use EV_EVENT_TAG instead, with a tag + * of our choosing that makes clear what precisely we are measuring here. See above. */ desc_len = strsize16(description); tcg_event = xmalloc(offsetof(EFI_TCG2_EVENT, Event) + desc_len); @@ -91,7 +92,7 @@ static EFI_STATUS tpm2_measure_to_pcr_and_event_log( tcg_event); } -static EFI_STATUS cc_measure_to_mr_and_event_log( +static EFI_STATUS cc_measure_to_mr_and_ipl_event_log( EFI_CC_MEASUREMENT_PROTOCOL *cc, uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, @@ -187,19 +188,24 @@ bool tpm_present(void) { return tcg2_interface_check(); } -static EFI_STATUS tcg2_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { +static EFI_STATUS tcg2_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { EFI_TCG2_PROTOCOL *tpm2; EFI_STATUS err = EFI_SUCCESS; assert(ret_measured); tpm2 = tcg2_interface_check(); - if (tpm2) - err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + if (!tpm2) { + *ret_measured = false; + return EFI_SUCCESS; + } - *ret_measured = tpm2 && (err == EFI_SUCCESS); + err = tpm2_measure_to_pcr_and_ipl_event_log(tpm2, pcrindex, buffer, buffer_size, description); + if (err != EFI_SUCCESS) + return err; - return err; + *ret_measured = true; + return EFI_SUCCESS; } static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { @@ -209,15 +215,20 @@ static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, s assert(ret_measured); cc = cc_interface_check(); - if (cc) - err = cc_measure_to_mr_and_event_log(cc, pcrindex, buffer, buffer_size, description); + if (!cc) { + *ret_measured = false; + return EFI_SUCCESS; + } - *ret_measured = cc && (err == EFI_SUCCESS); + err = cc_measure_to_mr_and_ipl_event_log(cc, pcrindex, buffer, buffer_size, description); + if (err != EFI_SUCCESS) + return err; - return err; + *ret_measured = true; + return EFI_SUCCESS; } -EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { +EFI_STATUS tpm_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { EFI_STATUS err; bool tpm_ret_measured, cc_ret_measured; @@ -238,12 +249,14 @@ EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t if (err != EFI_SUCCESS) return err; - err = tcg2_log_event(pcrindex, buffer, buffer_size, description, &tpm_ret_measured); + err = tcg2_log_ipl_event(pcrindex, buffer, buffer_size, description, &tpm_ret_measured); + if (err != EFI_SUCCESS) + return err; - if (err == EFI_SUCCESS && ret_measured) + if (ret_measured) *ret_measured = tpm_ret_measured || cc_ret_measured; - return err; + return EFI_SUCCESS; } EFI_STATUS tpm_log_tagged_event( @@ -272,42 +285,39 @@ EFI_STATUS tpm_log_tagged_event( } err = tpm2_measure_to_pcr_and_tagged_event_log(tpm2, pcrindex, buffer, buffer_size, event_id, description); - if (err == EFI_SUCCESS && ret_measured) - *ret_measured = true; + if (!err) + return err; - return err; + *ret_measured = true; + return EFI_SUCCESS; } -EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) { +EFI_STATUS tpm_log_ipl_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) { _cleanup_free_ char16_t *c = NULL; if (description) c = xstr8_to_16(description); - return tpm_log_event(pcrindex, buffer, buffer_size, c, ret_measured); + return tpm_log_ipl_event(pcrindex, buffer, buffer_size, c, ret_measured); } EFI_STATUS tpm_log_load_options(const char16_t *load_options, bool *ret_measured) { - bool measured = false; EFI_STATUS err; /* Measures a load options string into the TPM2, i.e. the kernel command line */ - err = tpm_log_event( + err = tpm_log_ipl_event( TPM2_PCR_KERNEL_CONFIG, POINTER_TO_PHYSICAL_ADDRESS(load_options), strsize16(load_options), load_options, - &measured); + ret_measured); if (err != EFI_SUCCESS) return log_error_status( err, "Unable to add load options (i.e. kernel command) line measurement to PCR %i: %m", TPM2_PCR_KERNEL_CONFIG); - if (ret_measured) - *ret_measured = measured; - return EFI_SUCCESS; } diff --git a/src/boot/efi/measure.h b/src/boot/efi/measure.h index c3c4e0a9ad..9dde93b94d 100644 --- a/src/boot/efi/measure.h +++ b/src/boot/efi/measure.h @@ -6,9 +6,20 @@ #if ENABLE_TPM bool tpm_present(void); -EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured); -EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured); + +/* Routines for boot-time TPM PCR measurement as well as submitting an event log entry about it. The latter + * can be done with two different event log record types. For old stuff we use EV_IPL (which is legacy, and + * not great to recognize properly during PCR validation). For new stuff we use properly tagged + * EV_EVENT_TAG record. */ + +/* Old stuff is logged as EV_IPL */ +EFI_STATUS tpm_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured); +EFI_STATUS tpm_log_ipl_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const +char *description, bool *ret_measured); + +/* New stuff is logged as EV_EVENT_TAG */ EFI_STATUS tpm_log_tagged_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, uint32_t event_id, const char16_t *description, bool *ret_measured); + EFI_STATUS tpm_log_load_options(const char16_t *cmdline, bool *ret_measured); #else @@ -17,13 +28,13 @@ static inline bool tpm_present(void) { return false; } -static inline EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { +static inline EFI_STATUS tpm_log_ipl_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { if (ret_measured) *ret_measured = false; return EFI_SUCCESS; } -static inline EFI_STATUS tpm_log_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) { +static inline EFI_STATUS tpm_log_ipl_event_ascii(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char *description, bool *ret_measured) { if (ret_measured) *ret_measured = false; return EFI_SUCCESS; diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 7a60b0ec7e..42a7914bf6 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -258,6 +258,7 @@ libefi_sources = files( 'devicetree.c', 'drivers.c', 'efi-string.c', + 'export-vars.c', 'graphics.c', 'initrd.c', 'log.c', diff --git a/src/boot/efi/pe.c b/src/boot/efi/pe.c index 829266b7f5..587e3ef7b1 100644 --- a/src/boot/efi/pe.c +++ b/src/boot/efi/pe.c @@ -101,7 +101,7 @@ typedef struct PeOptionalHeader { } _packed_ PeOptionalHeader; typedef struct PeFileHeader { - uint8_t Magic[4]; + uint8_t Magic[4]; CoffFileHeader FileHeader; PeOptionalHeader OptionalHeader; } _packed_ PeFileHeader; @@ -119,69 +119,134 @@ typedef struct PeSectionHeader { uint32_t Characteristics; } _packed_ PeSectionHeader; +#define SECTION_TABLE_BYTES_MAX (16U * 1024U * 1024U) + static bool verify_dos(const DosFileHeader *dos) { assert(dos); - return memcmp(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0; + + DISABLE_WARNING_TYPE_LIMITS; + return memcmp(dos->Magic, DOS_FILE_MAGIC, STRLEN(DOS_FILE_MAGIC)) == 0 && + dos->ExeHeader >= sizeof(DosFileHeader) && + (size_t) dos->ExeHeader <= SIZE_MAX - sizeof(PeFileHeader); + REENABLE_WARNING; } -static bool verify_pe(const PeFileHeader *pe, bool allow_compatibility) { +static bool verify_pe( + const DosFileHeader *dos, + const PeFileHeader *pe, + bool allow_compatibility) { + + assert(dos); assert(pe); + return memcmp(pe->Magic, PE_FILE_MAGIC, STRLEN(PE_FILE_MAGIC)) == 0 && - (pe->FileHeader.Machine == TARGET_MACHINE_TYPE || - (allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) && - pe->FileHeader.NumberOfSections > 0 && - pe->FileHeader.NumberOfSections <= MAX_SECTIONS && - IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC); + (pe->FileHeader.Machine == TARGET_MACHINE_TYPE || + (allow_compatibility && pe->FileHeader.Machine == TARGET_MACHINE_TYPE_COMPATIBILITY)) && + pe->FileHeader.NumberOfSections > 0 && + pe->FileHeader.NumberOfSections <= MAX_SECTIONS && + IN_SET(pe->OptionalHeader.Magic, OPTHDR32_MAGIC, OPTHDR64_MAGIC) && + pe->FileHeader.SizeOfOptionalHeader < SIZE_MAX - (dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader)); } static size_t section_table_offset(const DosFileHeader *dos, const PeFileHeader *pe) { assert(dos); assert(pe); + return dos->ExeHeader + offsetof(PeFileHeader, OptionalHeader) + pe->FileHeader.SizeOfOptionalHeader; } -static void locate_sections( +static bool pe_section_name_equal(const char *a, const char *b) { + + if (a == b) + return true; + if (!a != !b) + return false; + + /* Compares up to 8 characters of a and b i.e. the name size limit in the PE section header */ + + for (size_t i = 0; i < sizeof_field(PeSectionHeader, Name); i++) { + if (a[i] != b[i]) + return false; + + if (a[i] == 0) /* Name is shorter than 8 */ + return true; + } + + return true; +} + +static void pe_locate_sections( const PeSectionHeader section_table[], - size_t n_table, + size_t n_section_table, const char * const sections[], - size_t *offsets, - size_t *sizes, - bool in_memory) { + size_t validate_base, + PeSectionVector *ret_sections) { - assert(section_table); + assert(section_table || n_section_table == 0); assert(sections); - assert(offsets); - assert(sizes); + assert(ret_sections); + + /* Searches for the sections listed in 'sections[]' within the section table. Validates the resulted + * data. If 'validate_base' is non-zero also takes base offset when loaded into memory into account for + * qchecking for overflows. */ - for (size_t i = 0; i < n_table; i++) { - const PeSectionHeader *sect = section_table + i; + for (size_t i = 0; sections[i]; i++) + FOREACH_ARRAY(j, section_table, n_section_table) { + + if (!pe_section_name_equal((const char*) j->Name, sections[i])) + continue; - for (size_t j = 0; sections[j]; j++) { - if (memcmp(sect->Name, sections[j], strlen8(sections[j])) != 0) + /* Overflow check: ignore sections that are impossibly large, relative to the file + * address for the section. */ + size_t size_max = SIZE_MAX - j->PointerToRawData; + if ((size_t) j->VirtualSize > size_max) continue; - offsets[j] = in_memory ? sect->VirtualAddress : sect->PointerToRawData; - sizes[j] = sect->VirtualSize; + /* Overflow check: ignore sections that are impossibly large, given the virtual + * address for the section */ + size_max = SIZE_MAX - j->VirtualAddress; + if (j->VirtualSize > size_max) + continue; + + /* 2nd overflow check: ignore sections that are impossibly large also taking the + * loaded base into account. */ + if (validate_base != 0) { + if (validate_base > size_max) + continue; + size_max -= validate_base; + + if (j->VirtualAddress > size_max) + continue; + } + + /* At this time, the sizes and offsets have been validated. Store them away */ + ret_sections[i] = (PeSectionVector) { + .size = j->VirtualSize, + .file_offset = j->PointerToRawData, + .memory_offset = j->VirtualAddress, + }; + + /* First matching section wins, ignore the rest */ + break; } - } } static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const PeFileHeader *pe) { - size_t addr = 0, size = 0; static const char *sections[] = { ".compat", NULL }; + PeSectionVector vector = {}; /* 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. */ - locate_sections((const PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)), + pe_locate_sections( + (const PeSectionHeader *) ((const uint8_t *) dos + section_table_offset(dos, pe)), pe->FileHeader.NumberOfSections, sections, - &addr, - &size, - /*in_memory=*/true); + PTR_TO_SIZE(dos), + &vector); - if (size == 0) + if (vector.size == 0) /* not found */ return 0; typedef struct { @@ -191,6 +256,8 @@ static uint32_t get_compatibility_entry_address(const DosFileHeader *dos, const uint32_t entry_point; } _packed_ LinuxPeCompat1; + size_t addr = vector.memory_offset, size = vector.size; + while (size >= sizeof(LinuxPeCompat1) && addr % alignof(LinuxPeCompat1) == 0) { LinuxPeCompat1 *compat = (LinuxPeCompat1 *) ((uint8_t *) dos + addr); @@ -218,7 +285,7 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address) { return EFI_LOAD_ERROR; const PeFileHeader *pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader); - if (!verify_pe(pe, /* allow_compatibility= */ true)) + if (!verify_pe(dos, pe, /* allow_compatibility= */ true)) return EFI_LOAD_ERROR; /* Support for LINUX_INITRD_MEDIA_GUID was added in kernel stub 1.0. */ @@ -239,31 +306,34 @@ EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address) { return EFI_SUCCESS; } -EFI_STATUS pe_memory_locate_sections(const void *base, const char * const sections[], size_t *addrs, size_t *sizes) { +EFI_STATUS pe_memory_locate_sections( + const void *base, + const char* const sections[], + PeSectionVector *ret_sections) { + const DosFileHeader *dos; const PeFileHeader *pe; size_t offset; assert(base); assert(sections); - assert(addrs); - assert(sizes); + assert(ret_sections); dos = (const DosFileHeader *) base; if (!verify_dos(dos)) return EFI_LOAD_ERROR; - pe = (const PeFileHeader *) ((uint8_t *) base + dos->ExeHeader); - if (!verify_pe(pe, /* allow_compatibility= */ false)) + pe = (const PeFileHeader *) ((const uint8_t *) base + dos->ExeHeader); + if (!verify_pe(dos, pe, /* allow_compatibility= */ false)) return EFI_LOAD_ERROR; offset = section_table_offset(dos, pe); - locate_sections((PeSectionHeader *) ((uint8_t *) base + offset), + pe_locate_sections( + (const PeSectionHeader *) ((const uint8_t *) base + offset), pe->FileHeader.NumberOfSections, sections, - addrs, - sizes, - /*in_memory=*/true); + PTR_TO_SIZE(base), + ret_sections); return EFI_SUCCESS; } @@ -272,8 +342,7 @@ EFI_STATUS pe_file_locate_sections( EFI_FILE *dir, const char16_t *path, const char * const sections[], - size_t *offsets, - size_t *sizes) { + PeSectionVector *ret_sections) { _cleanup_free_ PeSectionHeader *section_table = NULL; _cleanup_(file_closep) EFI_FILE *handle = NULL; DosFileHeader dos; @@ -284,8 +353,7 @@ EFI_STATUS pe_file_locate_sections( assert(dir); assert(path); assert(sections); - assert(offsets); - assert(sizes); + assert(ret_sections); err = dir->Open(dir, &handle, (char16_t *) path, EFI_FILE_MODE_READ, 0ULL); if (err != EFI_SUCCESS) @@ -306,10 +374,16 @@ EFI_STATUS pe_file_locate_sections( err = handle->Read(handle, &len, &pe); if (err != EFI_SUCCESS) return err; - if (len != sizeof(pe) || !verify_pe(&pe, /* allow_compatibility= */ false)) + if (len != sizeof(pe) || !verify_pe(&dos, &pe, /* allow_compatibility= */ false)) return EFI_LOAD_ERROR; - section_table_len = pe.FileHeader.NumberOfSections * sizeof(PeSectionHeader); + DISABLE_WARNING_TYPE_LIMITS; + if ((size_t) pe.FileHeader.NumberOfSections > SIZE_MAX / sizeof(PeSectionHeader)) + return EFI_OUT_OF_RESOURCES; + REENABLE_WARNING; + section_table_len = (size_t) pe.FileHeader.NumberOfSections * sizeof(PeSectionHeader); + if (section_table_len > SECTION_TABLE_BYTES_MAX) + return EFI_OUT_OF_RESOURCES; section_table = xmalloc(section_table_len); if (!section_table) return EFI_OUT_OF_RESOURCES; @@ -325,8 +399,12 @@ EFI_STATUS pe_file_locate_sections( if (len != section_table_len) return EFI_LOAD_ERROR; - locate_sections(section_table, pe.FileHeader.NumberOfSections, - sections, offsets, sizes, /*in_memory=*/false); + pe_locate_sections( + section_table, + pe.FileHeader.NumberOfSections, + sections, + /* validate_base= */ 0, /* don't validate base */ + ret_sections); return EFI_SUCCESS; } diff --git a/src/boot/efi/pe.h b/src/boot/efi/pe.h index 7e2258fceb..bc6d74beeb 100644 --- a/src/boot/efi/pe.h +++ b/src/boot/efi/pe.h @@ -3,17 +3,27 @@ #include "efi.h" +/* This is a subset of the full PE section header structure, with validated values, and without + * the noise. */ +typedef struct PeSectionVector { + size_t size; + size_t memory_offset; /* Offset in memory, relative to base address */ + uint64_t file_offset; /* Offset on disk, relative to beginning of file */ +} PeSectionVector; + +static inline bool PE_SECTION_VECTOR_IS_SET(const PeSectionVector *v) { + return v && v->size != 0; +} + EFI_STATUS pe_memory_locate_sections( const void *base, const char * const sections[], - size_t *addrs, - size_t *sizes); + PeSectionVector *ret_sections); EFI_STATUS pe_file_locate_sections( EFI_FILE *dir, const char16_t *path, const char * const sections[], - size_t *offsets, - size_t *sizes); + PeSectionVector *ret_sections); EFI_STATUS pe_kernel_info(const void *base, uint32_t *ret_compat_address); diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 9aa605b756..f51812d0d1 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -3,7 +3,9 @@ #include "cpio.h" #include "device-path-util.h" #include "devicetree.h" +#include "export-vars.h" #include "graphics.h" +#include "iovec-util-fundamental.h" #include "linux.h" #include "measure.h" #include "memory-util-fundamental.h" @@ -21,28 +23,88 @@ #include "version.h" #include "vmm.h" +/* The list of initrds we combine into one, in the order we want to merge them */ +enum { + /* The first two are part of the PE binary */ + INITRD_UCODE, + INITRD_BASE, + + /* The rest are dynamically generated, and hence in dynamic memory */ + _INITRD_DYNAMIC_FIRST, + INITRD_CREDENTIAL = _INITRD_DYNAMIC_FIRST, + INITRD_GLOBAL_CREDENTIAL, + INITRD_SYSEXT, + INITRD_CONFEXT, + INITRD_PCRSIG, + INITRD_PCRPKEY, + _INITRD_MAX, +}; + /* magic string to find in the binary image */ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION " ####"); DECLARE_SBAT(SBAT_STUB_SECTION_TEXT); +static char16_t* pe_section_to_str16( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector *section) { + + assert(loaded_image); + assert(section); + + if (!PE_SECTION_VECTOR_IS_SET(section)) + return NULL; + + return xstrn8_to_16((const char *) loaded_image->ImageBase + section->memory_offset, section->size); +} + +static char *pe_section_to_str8( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector *section) { + + assert(loaded_image); + assert(section); + + if (!PE_SECTION_VECTOR_IS_SET(section)) + return NULL; + + return xstrndup8((const char *)loaded_image->ImageBase + section->memory_offset, section->size); +} + +static void combine_measured_flag(int *value, int measured) { + assert(value); + + /* Combine the "measured" flag in a sensible way: if we haven't measured anything yet, the first + * write is taken as is. Later writes can only turn off the flag, never on again. Or in other words, + * we eventually want to return true iff we really measured *everything* there was to measure. + * + * Reminder how the "measured" flag actually works: + * > 0 → something was measured + * == 0 → there was something to measure but we didn't (because no TPM or so) + * < 0 → nothing has been submitted for measurement so far + */ + + if (measured < 0) + return; + + *value = *value < 0 ? measured : *value && measured; +} + /* Combine initrds by concatenation in memory */ static EFI_STATUS combine_initrds( - const void * const initrds[], const size_t initrd_sizes[], size_t n_initrds, - Pages *ret_initr_pages, size_t *ret_initrd_size) { + struct iovec initrds[], size_t n_initrds, + Pages *ret_initrd_pages, size_t *ret_initrd_size) { size_t n = 0; - assert(ret_initr_pages); + assert(initrds || n_initrds == 0); + assert(ret_initrd_pages); assert(ret_initrd_size); - for (size_t i = 0; i < n_initrds; i++) { - if (!initrds[i]) - continue; + FOREACH_ARRAY(i, initrds, n_initrds) { + /* some initrds (the ones from UKI sections) need padding, pad all to be safe */ - /* some initrds (the ones from UKI sections) need padding, - * pad all to be safe */ - size_t initrd_size = ALIGN4(initrd_sizes[i]); + size_t initrd_size = ALIGN4(i->iov_len); if (n > SIZE_MAX - initrd_size) return EFI_OUT_OF_RESOURCES; @@ -55,31 +117,29 @@ static EFI_STATUS combine_initrds( EFI_SIZE_TO_PAGES(n), UINT32_MAX /* Below 4G boundary. */); uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); - for (size_t i = 0; i < n_initrds; i++) { - if (!initrds[i]) - continue; - + FOREACH_ARRAY(i, initrds, n_initrds) { size_t pad; - p = mempcpy(p, initrds[i], initrd_sizes[i]); + p = mempcpy(p, i->iov_base, i->iov_len); - pad = ALIGN4(initrd_sizes[i]) - initrd_sizes[i]; - if (pad > 0) { - memzero(p, pad); - p += pad; - } + pad = ALIGN4(i->iov_len) - i->iov_len; + if (pad == 0) + continue; + + memzero(p, pad); + p += pad; } assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); - *ret_initr_pages = pages; + *ret_initrd_pages = pages; *ret_initrd_size = n; pages.n_pages = 0; return EFI_SUCCESS; } -static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { +static void export_stub_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { static const uint64_t stub_features = EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */ EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */ @@ -94,41 +154,6 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { assert(loaded_image); - /* Export the device path this image is started from, if it's not set yet */ - if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) { - _cleanup_free_ char16_t *uuid = disk_get_part_uuid(loaded_image->DeviceHandle); - if (uuid) - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderDevicePartUUID", uuid, 0); - } - - /* If LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from the - * UEFI firmware without any boot loader, and hence set the LoaderImageIdentifier ourselves. Note - * that some boot chain loaders neither set LoaderImageIdentifier nor make FilePath available to us, - * in which case there's simple nothing to set for us. (The UEFI spec doesn't really say who's wrong - * here, i.e. whether FilePath may be NULL or not, hence handle this gracefully and check if FilePath - * is non-NULL explicitly.) */ - if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS && - loaded_image->FilePath) { - _cleanup_free_ char16_t *s = NULL; - if (device_path_to_str(loaded_image->FilePath, &s) == EFI_SUCCESS) - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderImageIdentifier", s, 0); - } - - /* if LoaderFirmwareInfo is not set, let's set it */ - if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) { - _cleanup_free_ char16_t *s = NULL; - s = xasprintf("%ls %u.%02u", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareInfo", s, 0); - } - - /* ditto for LoaderFirmwareType */ - if (efivar_get_raw(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) { - _cleanup_free_ char16_t *s = NULL; - s = xasprintf("UEFI %u.%02u", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); - efivar_set(MAKE_GUID_PTR(LOADER), u"LoaderFirmwareType", s, 0); - } - - /* add StubInfo (this is one is owned by the stub, hence we unconditionally override this with our * own data) */ (void) efivar_set(MAKE_GUID_PTR(LOADER), u"StubInfo", u"systemd-stub " GIT_VERSION, 0); @@ -152,20 +177,19 @@ static bool use_load_options( if (secure_boot_enabled() && (have_cmdline || is_confidential_vm())) return false; - /* We also do a superficial check whether first character of passed command line - * is printable character (for compat with some Dell systems which fill in garbage?). */ - if (loaded_image->LoadOptionsSize < sizeof(char16_t) || ((char16_t *) loaded_image->LoadOptions)[0] <= 0x1F) - return false; - /* The UEFI shell registers EFI_SHELL_PARAMETERS_PROTOCOL onto images it runs. This lets us know that * LoadOptions starts with the stub binary path which we want to strip off. */ EFI_SHELL_PARAMETERS_PROTOCOL *shell; - if (BS->HandleProtocol(stub_image, MAKE_GUID_PTR(EFI_SHELL_PARAMETERS_PROTOCOL), (void **) &shell) - != EFI_SUCCESS) { + if (BS->HandleProtocol(stub_image, MAKE_GUID_PTR(EFI_SHELL_PARAMETERS_PROTOCOL), (void **) &shell) != EFI_SUCCESS) { + + /* We also do a superficial check whether first character of passed command line + * is printable character (for compat with some Dell systems which fill in garbage?). */ + if (loaded_image->LoadOptionsSize < sizeof(char16_t) || ((const char16_t *) loaded_image->LoadOptions)[0] <= 0x1F) + return false; + /* Not running from EFI shell, use entire LoadOptions. Note that LoadOptions is a void*, so * it could be anything! */ - *ret = xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t)); - mangle_stub_cmdline(*ret); + *ret = mangle_stub_cmdline(xstrndup16(loaded_image->LoadOptions, loaded_image->LoadOptionsSize / sizeof(char16_t))); return true; } @@ -180,7 +204,6 @@ static bool use_load_options( *ret = xasprintf("%ls %ls", old, shell->Argv[i]); } - mangle_stub_cmdline(*ret); return true; } @@ -249,96 +272,88 @@ static EFI_STATUS load_addons_from_dir( } static void cmdline_append_and_measure_addons( - char16_t *cmdline_global, - char16_t *cmdline_uki, + char16_t *cmdline_addon, char16_t **cmdline_append, - bool *ret_parameters_measured) { - - _cleanup_free_ char16_t *tmp = NULL, *merged = NULL; - bool m = false; + int *parameters_measured) { assert(cmdline_append); - assert(ret_parameters_measured); + assert(parameters_measured); - if (isempty(cmdline_global) && isempty(cmdline_uki)) + if (isempty(cmdline_addon)) return; - merged = xasprintf("%ls%ls%ls", - strempty(cmdline_global), - isempty(cmdline_global) || isempty(cmdline_uki) ? u"" : u" ", - strempty(cmdline_uki)); - - mangle_stub_cmdline(merged); - - if (isempty(merged)) + _cleanup_free_ char16_t *copy = mangle_stub_cmdline(xstrdup16(cmdline_addon)); + if (isempty(copy)) return; - (void) tpm_log_load_options(merged, &m); - *ret_parameters_measured = m; - - tmp = TAKE_PTR(*cmdline_append); - *cmdline_append = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", merged); + bool m = false; + (void) tpm_log_load_options(copy, &m); + combine_measured_flag(parameters_measured, m); + + _cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline_append); + if (isempty(tmp)) + *cmdline_append = TAKE_PTR(copy); + else + *cmdline_append = xasprintf("%ls %ls", tmp, copy); } -static void dtb_install_addons( - struct devicetree_state *dt_state, - void **dt_bases, - size_t *dt_sizes, - char16_t **dt_filenames, - size_t n_dts, - bool *ret_parameters_measured) { +typedef struct DevicetreeAddon { + char16_t *filename; + struct iovec blob; +} DevicetreeAddon; - int parameters_measured = -1; - EFI_STATUS err; +static void devicetree_addon_done(DevicetreeAddon *a) { + assert(a); - assert(dt_state); - assert(n_dts == 0 || (dt_bases && dt_sizes && dt_filenames)); - assert(ret_parameters_measured); + a->filename = mfree(a->filename); + iovec_done(&a->blob); +} - for (size_t i = 0; i < n_dts; ++i) { - err = devicetree_install_from_memory(dt_state, dt_bases[i], dt_sizes[i]); - if (err != EFI_SUCCESS) - log_error_status(err, "Error loading addon devicetree, ignoring: %m"); - else { - bool m = false; +static void devicetree_addon_free_many(DevicetreeAddon *a, size_t n) { + assert(a || n == 0); - err = tpm_log_tagged_event( - TPM2_PCR_KERNEL_CONFIG, - POINTER_TO_PHYSICAL_ADDRESS(dt_bases[i]), - dt_sizes[i], - DEVICETREE_ADDON_EVENT_TAG_ID, - dt_filenames[i], - &m); - if (err != EFI_SUCCESS) - return (void) log_error_status( - err, - "Unable to add measurement of DTB addon #%zu to PCR %i: %m", - i, - TPM2_PCR_KERNEL_CONFIG); - - parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - } - } + FOREACH_ARRAY(i, a, n) + devicetree_addon_done(i); - *ret_parameters_measured = parameters_measured; + free(a); } -static void dt_bases_free(void **dt_bases, size_t n_dt) { - assert(dt_bases || n_dt == 0); +static void install_addon_devicetrees( + struct devicetree_state *dt_state, + DevicetreeAddon *addons, + size_t n_addons, + int *parameters_measured) { - for (size_t i = 0; i < n_dt; ++i) - free(dt_bases[i]); + EFI_STATUS err; - free(dt_bases); -} + assert(dt_state); + assert(addons || n_addons == 0); + assert(parameters_measured); -static void dt_filenames_free(char16_t **dt_filenames, size_t n_dt) { - assert(dt_filenames || n_dt == 0); + FOREACH_ARRAY(a, addons, n_addons) { + err = devicetree_install_from_memory(dt_state, a->blob.iov_base, a->blob.iov_len); + if (err != EFI_SUCCESS) { + log_error_status(err, "Error loading addon devicetree, ignoring: %m"); + continue; + } - for (size_t i = 0; i < n_dt; ++i) - free(dt_filenames[i]); + bool m = false; + err = tpm_log_tagged_event( + TPM2_PCR_KERNEL_CONFIG, + POINTER_TO_PHYSICAL_ADDRESS(a->blob.iov_base), + a->blob.iov_len, + DEVICETREE_ADDON_EVENT_TAG_ID, + a->filename, + &m); + if (err != EFI_SUCCESS) + return (void) log_error_status( + err, + "Unable to extend PCR %i with DTB addon '%ls': %m", + TPM2_PCR_KERNEL_CONFIG, + a->filename); - free(dt_filenames); + combine_measured_flag(parameters_measured, m); + } } static EFI_STATUS load_addons( @@ -346,34 +361,22 @@ static EFI_STATUS load_addons( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *prefix, const char *uname, - char16_t **ret_cmdline, - void ***ret_dt_bases, - size_t **ret_dt_sizes, - char16_t ***ret_dt_filenames, - size_t *ret_n_dt) { + char16_t **cmdline, /* Both input+output, extended with new addons we find */ + DevicetreeAddon **devicetree_addons, /* Ditto */ + size_t *n_devicetree_addons) { - _cleanup_free_ size_t *dt_sizes = NULL; _cleanup_(strv_freep) char16_t **items = NULL; _cleanup_(file_closep) EFI_FILE *root = NULL; - _cleanup_free_ char16_t *cmdline = NULL; - size_t n_items = 0, n_allocated = 0, n_dt = 0; - char16_t **dt_filenames = NULL; - void **dt_bases = NULL; + size_t n_items = 0, n_allocated = 0; EFI_STATUS err; assert(stub_image); assert(loaded_image); assert(prefix); - assert(!!ret_dt_bases == !!ret_dt_sizes); - assert(!!ret_dt_bases == !!ret_n_dt); - assert(!!ret_dt_filenames == !!ret_n_dt); if (!loaded_image->DeviceHandle) return EFI_SUCCESS; - CLEANUP_ARRAY(dt_bases, n_dt, dt_bases_free); - CLEANUP_ARRAY(dt_filenames, n_dt, dt_filenames_free); - err = open_volume(loaded_image->DeviceHandle, &root); if (err == EFI_UNSUPPORTED) /* Error will be unsupported if the bootloader doesn't implement the file system protocol on @@ -394,7 +397,7 @@ static EFI_STATUS load_addons( sort_pointer_array((void**) items, n_items, (compare_pointer_func_t) strcmp16); for (size_t i = 0; i < n_items; i++) { - size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; + PeSectionVector sections[ELEMENTSOF(unified_sections)] = {}; _cleanup_free_ EFI_DEVICE_PATH *addon_path = NULL; _cleanup_(unload_imagep) EFI_HANDLE addon = NULL; EFI_LOADED_IMAGE_PROTOCOL *loaded_addon = NULL; @@ -422,9 +425,10 @@ 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, addrs, szs); + err = pe_memory_locate_sections(loaded_addon->ImageBase, unified_sections, sections); if (err != EFI_SUCCESS || - (szs[UNIFIED_SECTION_CMDLINE] == 0 && szs[UNIFIED_SECTION_DTB] == 0)) { + (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE) && + !PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB))) { if (err == EFI_SUCCESS) err = EFI_NOT_FOUND; log_error_status(err, @@ -434,141 +438,80 @@ static EFI_STATUS load_addons( } /* We want to enforce that addons are not UKIs, i.e.: they must not embed a kernel. */ - if (szs[UNIFIED_SECTION_LINUX] > 0) { - log_error_status(EFI_INVALID_PARAMETER, "%ls is a UKI, not an addon, ignoring: %m", items[i]); + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_LINUX)) { + log_error("%ls is a UKI, not an addon, ignoring.", items[i]); continue; } /* Also enforce that, in case it is specified, .uname matches as a quick way to allow * enforcing compatibility with a specific UKI only */ - if (uname && szs[UNIFIED_SECTION_UNAME] > 0 && + if (uname && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UNAME) && !strneq8(uname, - (char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_UNAME], - szs[UNIFIED_SECTION_UNAME])) { + (const char *)loaded_addon->ImageBase + sections[UNIFIED_SECTION_UNAME].memory_offset, + sections[UNIFIED_SECTION_UNAME].size)) { log_error(".uname mismatch between %ls and UKI, ignoring", items[i]); continue; } - if (ret_cmdline && szs[UNIFIED_SECTION_CMDLINE] > 0) { - _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), - *extra16 = xstrn8_to_16((char *)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_CMDLINE], - szs[UNIFIED_SECTION_CMDLINE]); - cmdline = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16); - } - - if (ret_dt_bases && szs[UNIFIED_SECTION_DTB] > 0) { - dt_sizes = xrealloc(dt_sizes, - n_dt * sizeof(size_t), - (n_dt + 1) * sizeof(size_t)); - dt_sizes[n_dt] = szs[UNIFIED_SECTION_DTB]; - - dt_bases = xrealloc(dt_bases, - n_dt * sizeof(void *), - (n_dt + 1) * sizeof(void *)); - dt_bases[n_dt] = xmemdup((uint8_t*)loaded_addon->ImageBase + addrs[UNIFIED_SECTION_DTB], - dt_sizes[n_dt]); - - dt_filenames = xrealloc(dt_filenames, - n_dt * sizeof(char16_t *), - (n_dt + 1) * sizeof(char16_t *)); - dt_filenames[n_dt] = xstrdup16(items[i]); + if (cmdline && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE)) { + _cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline), + *extra16 = mangle_stub_cmdline(pe_section_to_str16(loaded_addon, sections + UNIFIED_SECTION_CMDLINE)); - ++n_dt; + *cmdline = xasprintf("%ls%ls%ls", strempty(tmp), isempty(tmp) ? u"" : u" ", extra16); } - } - if (ret_cmdline && !isempty(cmdline)) - *ret_cmdline = TAKE_PTR(cmdline); + if (devicetree_addons && PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) { + *devicetree_addons = xrealloc(*devicetree_addons, + *n_devicetree_addons * sizeof(size_t), + (*n_devicetree_addons + 1) * sizeof(size_t)); - if (ret_n_dt && n_dt > 0) { - *ret_dt_filenames = TAKE_PTR(dt_filenames); - *ret_dt_bases = TAKE_PTR(dt_bases); - *ret_dt_sizes = TAKE_PTR(dt_sizes); - *ret_n_dt = n_dt; + *devicetree_addons[(*n_devicetree_addons)++] = (DevicetreeAddon) { + .blob = { + .iov_base = xmemdup((const uint8_t*) loaded_addon->ImageBase + sections[UNIFIED_SECTION_DTB].memory_offset, sections[UNIFIED_SECTION_DTB].size), + .iov_len = sections[UNIFIED_SECTION_DTB].size, + }, + .filename = xstrdup16(items[i]), + }; + } } return EFI_SUCCESS; } -static EFI_STATUS run(EFI_HANDLE image) { - _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *confext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL; - size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, confext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0; - void **dt_bases_addons_global = NULL, **dt_bases_addons_uki = NULL; - char16_t **dt_filenames_addons_global = NULL, **dt_filenames_addons_uki = NULL; - _cleanup_free_ size_t *dt_sizes_addons_global = NULL, *dt_sizes_addons_uki = NULL; - size_t linux_size, initrd_size, ucode_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0; - EFI_PHYSICAL_ADDRESS linux_base, initrd_base, ucode_base, dt_base; - _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; - EFI_LOADED_IMAGE_PROTOCOL *loaded_image; - size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; - _cleanup_free_ char16_t *cmdline = NULL, *cmdline_addons_global = NULL, *cmdline_addons_uki = NULL; - int sections_measured = -1, parameters_measured = -1; - _cleanup_free_ char *uname = NULL; - bool sysext_measured = false, confext_measured = false, m; - uint64_t loader_features = 0; +static void refresh_random_seed(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { EFI_STATUS err; - err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image); - if (err != EFI_SUCCESS) - return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m"); + assert(loaded_image); - if (loaded_image->DeviceHandle && /* Handle case, where bootloader doesn't support DeviceHandle. */ - (efivar_get_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", &loader_features) != EFI_SUCCESS || - !FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED))) { - _cleanup_(file_closep) EFI_FILE *esp_dir = NULL; + /* Handle case, where bootloader doesn't support DeviceHandle. */ + if (!loaded_image->DeviceHandle) + return; - err = partition_open(MAKE_GUID_PTR(ESP), loaded_image->DeviceHandle, NULL, &esp_dir); - if (err == EFI_SUCCESS) /* Non-fatal on failure, so that we still boot without it. */ - (void) process_random_seed(esp_dir); - } + uint64_t loader_features = 0; + err = efivar_get_uint64_le(MAKE_GUID_PTR(LOADER), u"LoaderFeatures", &loader_features); + if (err != EFI_SUCCESS) + return; - err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, addrs, szs); - if (err != EFI_SUCCESS || szs[UNIFIED_SECTION_LINUX] == 0) { - if (err == EFI_SUCCESS) - err = EFI_NOT_FOUND; - return log_error_status(err, "Unable to locate embedded .linux section: %m"); - } + /* Don't measure again, if sd-boot already initialized the random seed */ + if (!FLAGS_SET(loader_features, EFI_LOADER_FEATURE_RANDOM_SEED)) + return; - CLEANUP_ARRAY(dt_bases_addons_global, n_dts_addons_global, dt_bases_free); - CLEANUP_ARRAY(dt_bases_addons_uki, n_dts_addons_uki, dt_bases_free); - CLEANUP_ARRAY(dt_filenames_addons_global, n_dts_addons_global, dt_filenames_free); - CLEANUP_ARRAY(dt_filenames_addons_uki, n_dts_addons_uki, dt_filenames_free); + _cleanup_(file_closep) EFI_FILE *esp_dir = NULL; + err = partition_open(MAKE_GUID_PTR(ESP), loaded_image->DeviceHandle, NULL, &esp_dir); + if (err != EFI_SUCCESS) /* Non-fatal on failure, so that we still boot without it. */ + return; - if (szs[UNIFIED_SECTION_UNAME] > 0) - uname = xstrndup8((char *)loaded_image->ImageBase + addrs[UNIFIED_SECTION_UNAME], - szs[UNIFIED_SECTION_UNAME]); + (void) process_random_seed(esp_dir); +} - /* Now that we have the UKI sections loaded, also load global first and then local (per-UKI) - * addons. The data is loaded at once, and then used later. */ - err = load_addons( - image, - loaded_image, - u"\\loader\\addons", - uname, - &cmdline_addons_global, - &dt_bases_addons_global, - &dt_sizes_addons_global, - &dt_filenames_addons_global, - &n_dts_addons_global); - if (err != EFI_SUCCESS) - log_error_status(err, "Error loading global addons, ignoring: %m"); +static void measure_sections( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector sections[static _UNIFIED_SECTION_MAX], + int *sections_measured) { - /* Some bootloaders always pass NULL in FilePath, so we need to check for it here. */ - _cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath); - if (dropin_dir) { - err = load_addons( - image, - loaded_image, - dropin_dir, - uname, - &cmdline_addons_uki, - &dt_bases_addons_uki, - &dt_sizes_addons_uki, - &dt_filenames_addons_uki, - &n_dts_addons_uki); - if (err != EFI_SUCCESS) - log_error_status(err, "Error loading UKI-specific addons, ignoring: %m"); - } + assert(loaded_image); + assert(sections); + assert(sections_measured); /* Measure all "payload" of this PE image into a separate PCR (i.e. where nothing else is written * into so far), so that we have one PCR that we can nicely write policies against because it @@ -578,80 +521,102 @@ static EFI_STATUS run(EFI_HANDLE image) { if (!unified_section_measure(section)) /* shall not measure? */ continue; - if (szs[section] == 0) /* not found */ + if (!PE_SECTION_VECTOR_IS_SET(sections + section)) /* not found */ continue; - m = false; - /* First measure the name of the section */ - (void) tpm_log_event_ascii( + bool m = false; + (void) tpm_log_ipl_event_ascii( TPM2_PCR_KERNEL_BOOT, POINTER_TO_PHYSICAL_ADDRESS(unified_sections[section]), strsize8(unified_sections[section]), /* including NUL byte */ unified_sections[section], &m); - - sections_measured = sections_measured < 0 ? m : (sections_measured && m); + combine_measured_flag(sections_measured, m); /* Then measure the data of the section */ - (void) tpm_log_event_ascii( + m = false; + (void) tpm_log_ipl_event_ascii( TPM2_PCR_KERNEL_BOOT, - POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[section], - szs[section], + POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + sections[section].memory_offset, + sections[section].size, unified_sections[section], &m); - - sections_measured = sections_measured < 0 ? m : (sections_measured && m); + combine_measured_flag(sections_measured, m); } +} - /* After we are done, set an EFI variable that tells userspace this was done successfully, and encode - * in it which PCR was used. */ - if (sections_measured > 0) - (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelImage", TPM2_PCR_KERNEL_BOOT, 0); +static void cmdline_append_and_measure_smbios(char16_t **cmdline, int *parameters_measured) { + assert(cmdline); + assert(parameters_measured); - /* Show splash screen as early as possible */ - graphics_splash((const uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_SPLASH], szs[UNIFIED_SECTION_SPLASH]); + /* SMBIOS OEM Strings data is controlled by the host admin and not covered by the VM attestation, so + * MUST NOT be trusted when in a confidential VM */ + if (is_confidential_vm()) + return; - if (use_load_options(image, loaded_image, szs[UNIFIED_SECTION_CMDLINE] > 0, &cmdline)) { - /* Let's measure the passed kernel command line into the TPM. Note that this possibly - * duplicates what we already did in the boot menu, if that was already used. However, since - * we want the boot menu to support an EFI binary, and want to this stub to be usable from - * any boot menu, let's measure things anyway. */ - m = false; - (void) tpm_log_load_options(cmdline, &m); - parameters_measured = m; - } else if (szs[UNIFIED_SECTION_CMDLINE] > 0) { - cmdline = xstrn8_to_16( - (char *) loaded_image->ImageBase + addrs[UNIFIED_SECTION_CMDLINE], - szs[UNIFIED_SECTION_CMDLINE]); - mangle_stub_cmdline(cmdline); - } + const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra"); + if (!extra) + return; - /* If we have any extra command line to add via PE addons, load them now and append, and - * measure the additions together, after the embedded options, but before the smbios ones, - * so that the order is reversed from "most hardcoded" to "most dynamic". The global addons are - * loaded first, and the image-specific ones later, for the same reason. */ - cmdline_append_and_measure_addons(cmdline_addons_global, cmdline_addons_uki, &cmdline, &m); - parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - - /* SMBIOS OEM Strings data is controlled by the host admin and not covered - * by the VM attestation, so MUST NOT be trusted when in a confidential VM */ - if (!is_confidential_vm()) { - const char *extra = smbios_find_oem_string("io.systemd.stub.kernel-cmdline-extra"); - if (extra) { - _cleanup_free_ char16_t *tmp = TAKE_PTR(cmdline), *extra16 = xstr8_to_16(extra); - cmdline = xasprintf("%ls %ls", tmp, extra16); - - /* SMBIOS strings are measured in PCR1, but we also want to measure them in our specific - * PCR12, as firmware-owned PCRs are very difficult to use as they'll contain unpredictable - * measurements that are not under control of the machine owner. */ - m = false; - (void) tpm_log_load_options(extra16, &m); - parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - } + _cleanup_free_ char16_t *extra16 = mangle_stub_cmdline(xstr8_to_16(extra)); + if (isempty(extra16)) + return; + + /* SMBIOS strings are measured in PCR1, but we also want to measure them in our specific PCR12, as + * firmware-owned PCRs are very difficult to use as they'll contain unpredictable measurements that + * are not under control of the machine owner. */ + bool m = false; + (void) tpm_log_load_options(extra16, &m); + combine_measured_flag(parameters_measured, m); + + _cleanup_free_ char16_t *tmp = TAKE_PTR(*cmdline); + if (isempty(tmp)) + *cmdline = TAKE_PTR(extra16); + else + *cmdline = xasprintf("%ls %ls", tmp, extra16); +} + +static void initrds_free(struct iovec (*initrds)[_INITRD_MAX]) { + assert(initrds); + + /* Free the dynamic initrds, but leave the non-dynamic ones around */ + + for (size_t i = _INITRD_DYNAMIC_FIRST; i < _INITRD_MAX; i++) + iovec_done((*initrds) + i); +} + +static bool initrds_need_combine(struct iovec initrds[static _INITRD_MAX]) { + assert(initrds); + + /* Returns true if we have any initrds set that aren't the base initrd. In that case we need to + * merge, otherwise we can pass the embedded initrd as is */ + + for (size_t i = 0; i < _INITRD_MAX; i++) { + if (i == INITRD_BASE) + continue; + + if (iovec_is_set(initrds + i)) + return true; } - export_variables(loaded_image); + return false; +} + +static void generate_sidecar_initrds( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + struct iovec initrds[static _INITRD_MAX], + int *parameters_measured, + int *sysext_measured, + int *confext_measured) { + + bool m; + + assert(loaded_image); + assert(initrds); + assert(parameters_measured); + assert(sysext_measured); + assert(confext_measured); if (pack_cpio(loaded_image, /* dropin_dir= */ NULL, @@ -662,10 +627,9 @@ static EFI_STATUS run(EFI_HANDLE image) { /* access_mode= */ 0400, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Credentials initrd", - &credential_initrd, - &credential_initrd_size, + initrds + INITRD_CREDENTIAL, &m) == EFI_SUCCESS) - parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); + combine_measured_flag(parameters_measured, m); if (pack_cpio(loaded_image, u"\\loader\\credentials", @@ -676,10 +640,9 @@ static EFI_STATUS run(EFI_HANDLE image) { /* access_mode= */ 0400, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Global credentials initrd", - &global_credential_initrd, - &global_credential_initrd_size, + initrds + INITRD_GLOBAL_CREDENTIAL, &m) == EFI_SUCCESS) - parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); + combine_measured_flag(parameters_measured, m); if (pack_cpio(loaded_image, /* dropin_dir= */ NULL, @@ -690,10 +653,9 @@ static EFI_STATUS run(EFI_HANDLE image) { /* access_mode= */ 0444, /* tpm_pcr= */ TPM2_PCR_SYSEXTS, u"System extension initrd", - &sysext_initrd, - &sysext_initrd_size, + initrds + INITRD_CONFEXT, &m) == EFI_SUCCESS) - sysext_measured = m; + combine_measured_flag(sysext_measured, m); if (pack_cpio(loaded_image, /* dropin_dir= */ NULL, @@ -704,137 +666,278 @@ static EFI_STATUS run(EFI_HANDLE image) { /* access_mode= */ 0444, /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, u"Configuration extension initrd", - &confext_initrd, - &confext_initrd_size, + initrds + INITRD_SYSEXT, &m) == EFI_SUCCESS) - confext_measured = m; - - dt_size = szs[UNIFIED_SECTION_DTB]; - dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0; - - /* First load the base device tree, then fix it up using addons - global first, then per-UKI. */ - if (dt_size > 0) { - err = devicetree_install_from_memory( - &dt_state, PHYSICAL_ADDRESS_TO_POINTER(dt_base), dt_size); - if (err != EFI_SUCCESS) - log_error_status(err, "Error loading embedded devicetree: %m"); - } + combine_measured_flag(confext_measured, m); +} - dtb_install_addons(&dt_state, - dt_bases_addons_global, - dt_sizes_addons_global, - dt_filenames_addons_global, - n_dts_addons_global, - &m); - parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); - dtb_install_addons(&dt_state, - dt_bases_addons_uki, - dt_sizes_addons_uki, - dt_filenames_addons_uki, - n_dts_addons_uki, - &m); - parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); +static void generate_embedded_initrds( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + PeSectionVector sections[_UNIFIED_SECTION_MAX], + struct iovec initrds[static _INITRD_MAX]) { - if (parameters_measured > 0) - (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0); - if (sysext_measured) - (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM2_PCR_SYSEXTS, 0); - if (confext_measured) - (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDConfExts", TPM2_PCR_KERNEL_CONFIG, 0); + assert(loaded_image); + assert(initrds); /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section * is not measured, neither as raw section (see above), nor as cpio (here), because it is the * signature of expected PCR values, i.e. its input are PCR measurements, and hence it shouldn't * itself be input for PCR measurements. */ - if (szs[UNIFIED_SECTION_PCRSIG] > 0) + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_PCRSIG)) (void) pack_cpio_literal( - (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRSIG], - szs[UNIFIED_SECTION_PCRSIG], + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_PCRSIG].memory_offset, + sections[UNIFIED_SECTION_PCRSIG].size, ".extra", u"tpm2-pcr-signature.json", /* dir_mode= */ 0555, /* access_mode= */ 0444, /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, - &pcrsig_initrd, - &pcrsig_initrd_size, + initrds + INITRD_PCRSIG, /* ret_measured= */ NULL); /* If the public key used for the PCR signatures was embedded in the PE image, then let's wrap it in * a cpio and also pass it to the kernel, so that it can be read from * /.extra/tpm2-pcr-public-key.pem. This section is already measure above, hence we won't measure the * cpio. */ - if (szs[UNIFIED_SECTION_PCRPKEY] > 0) + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_PCRPKEY)) (void) pack_cpio_literal( - (uint8_t*) loaded_image->ImageBase + addrs[UNIFIED_SECTION_PCRPKEY], - szs[UNIFIED_SECTION_PCRPKEY], + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_PCRPKEY].memory_offset, + sections[UNIFIED_SECTION_PCRPKEY].size, ".extra", u"tpm2-pcr-public-key.pem", /* dir_mode= */ 0555, /* access_mode= */ 0444, /* tpm_pcr= */ UINT32_MAX, /* tpm_description= */ NULL, - &pcrpkey_initrd, - &pcrpkey_initrd_size, + initrds + INITRD_PCRPKEY, /* ret_measured= */ NULL); +} - linux_size = szs[UNIFIED_SECTION_LINUX]; - linux_base = POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_LINUX]; +static void lookup_embedded_initrds( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + PeSectionVector sections[_UNIFIED_SECTION_MAX], + struct iovec initrds[static _INITRD_MAX]) { - initrd_size = szs[UNIFIED_SECTION_INITRD]; - initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0; + assert(loaded_image); + assert(sections); + assert(initrds); + + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_INITRD)) + initrds[INITRD_BASE] = IOVEC_MAKE( + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_INITRD].memory_offset, + sections[UNIFIED_SECTION_INITRD].size); + + if (PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_UCODE)) + initrds[INITRD_UCODE] = IOVEC_MAKE( + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_UCODE].memory_offset, + sections[UNIFIED_SECTION_UCODE].size); +} - ucode_size = szs[UNIFIED_SECTION_UCODE]; - ucode_base = ucode_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_UCODE] : 0; +static void export_pcr_variables( + int sections_measured, + int parameters_measured, + int sysext_measured, + int confext_measured) { + /* After we are done with measuring, set an EFI variable that tells userspace this was done + * successfully, and encode in it which PCR was used. */ + + if (sections_measured > 0) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelImage", TPM2_PCR_KERNEL_BOOT, 0); + if (parameters_measured > 0) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0); + if (sysext_measured > 0) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM2_PCR_SYSEXTS, 0); + if (confext_measured > 0) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDConfExts", TPM2_PCR_KERNEL_CONFIG, 0); +} + +static void install_embedded_devicetree( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector sections[static _UNIFIED_SECTION_MAX], + struct devicetree_state *dt_state) { + + EFI_STATUS err; + + assert(loaded_image); + assert(sections); + assert(dt_state); + + if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_DTB)) + return; + + err = devicetree_install_from_memory( + dt_state, + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_DTB].memory_offset, + sections[UNIFIED_SECTION_DTB].size); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading embedded devicetree, igoring: %m"); +} + +static void load_all_addons( + EFI_HANDLE image, + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const char *uname, + char16_t **cmdline_addons, + DevicetreeAddon **dt_addons, + size_t *n_dt_addons) { + + EFI_STATUS err; + + assert(loaded_image); + assert(cmdline_addons); + assert(dt_addons); + assert(n_dt_addons); + + err = load_addons( + image, + loaded_image, + u"\\loader\\addons", + uname, + cmdline_addons, + dt_addons, + n_dt_addons); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading global addons, ignoring: %m"); + + /* Some bootloaders always pass NULL in FilePath, so we need to check for it here. */ + _cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath); + if (!dropin_dir) + return; + + err = load_addons( + image, + loaded_image, + dropin_dir, + uname, + cmdline_addons, + dt_addons, + n_dt_addons); + if (err != EFI_SUCCESS) + log_error_status(err, "Error loading UKI-specific addons, ignoring: %m"); +} + +static void display_splash( + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector sections[static _UNIFIED_SECTION_MAX]) { + + assert(loaded_image); + assert(sections); + + if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_SPLASH)) + return; + + graphics_splash((const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_SPLASH].memory_offset, sections[UNIFIED_SECTION_SPLASH].size); +} + +static void determine_cmdline( + EFI_HANDLE image, + EFI_LOADED_IMAGE_PROTOCOL *loaded_image, + const PeSectionVector sections[static _UNIFIED_SECTION_MAX], + char16_t **ret_cmdline, + int *parameters_measured) { + + assert(loaded_image); + assert(sections); + + if (use_load_options(image, loaded_image, /* have_cmdline= */ PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_CMDLINE), ret_cmdline)) { + /* Let's measure the passed kernel command line into the TPM. Note that this possibly + * duplicates what we already did in the boot menu, if that was already used. However, since + * we want the boot menu to support an EFI binary, and want to this stub to be usable from + * any boot menu, let's measure things anyway. */ + bool m = false; + (void) tpm_log_load_options(*ret_cmdline, &m); + combine_measured_flag(parameters_measured, m); + } else + *ret_cmdline = mangle_stub_cmdline(pe_section_to_str16(loaded_image, sections + UNIFIED_SECTION_CMDLINE)); +} + +static EFI_STATUS run(EFI_HANDLE image) { + int sections_measured = -1, parameters_measured = -1, sysext_measured = -1, confext_measured = -1; + _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; + _cleanup_free_ char16_t *cmdline = NULL, *cmdline_addons = NULL; + _cleanup_(initrds_free) struct iovec initrds[_INITRD_MAX] = {}; + PeSectionVector sections[ELEMENTSOF(unified_sections)] = {}; + EFI_LOADED_IMAGE_PROTOCOL *loaded_image; + _cleanup_free_ char *uname = NULL; + DevicetreeAddon *dt_addons = NULL; + size_t n_dt_addons = 0; + EFI_STATUS err; + + err = BS->HandleProtocol(image, MAKE_GUID_PTR(EFI_LOADED_IMAGE_PROTOCOL), (void **) &loaded_image); + if (err != EFI_SUCCESS) + return log_error_status(err, "Error getting a LoadedImageProtocol handle: %m"); + + err = pe_memory_locate_sections(loaded_image->ImageBase, unified_sections, sections); + if (err != EFI_SUCCESS) + return log_error_status(err, "Unable to locate embedded PE sections: %m"); + if (!PE_SECTION_VECTOR_IS_SET(sections + UNIFIED_SECTION_LINUX)) + return log_error_status(EFI_NOT_FOUND, "Image lacks .linux section."); + + measure_sections(loaded_image, sections, §ions_measured); + + /* Show splash screen as early as possible, but after measuring it */ + display_splash(loaded_image, sections); + + refresh_random_seed(loaded_image); + + uname = pe_section_to_str8(loaded_image, sections + UNIFIED_SECTION_UNAME); + + determine_cmdline(image, loaded_image, sections, &cmdline, ¶meters_measured); + + /* Now that we have the UKI sections loaded, also load global first and then local (per-UKI) + * addons. The data is loaded at once, and then used later. */ + CLEANUP_ARRAY(dt_addons, n_dt_addons, devicetree_addon_free_many); + load_all_addons(image, loaded_image, uname, &cmdline_addons, &dt_addons, &n_dt_addons); + + /* If we have any extra command line to add via PE addons, load them now and append, and measure the + * additions together, after the embedded options, but before the smbios ones, so that the order is + * reversed from "most hardcoded" to "most dynamic". The global addons are loaded first, and the + * image-specific ones later, for the same reason. */ + cmdline_append_and_measure_addons(cmdline_addons, &cmdline, ¶meters_measured); + cmdline_append_and_measure_smbios(&cmdline, ¶meters_measured); + + export_common_variables(loaded_image); + export_stub_variables(loaded_image); + + /* First load the base device tree, then fix it up using addons - global first, then per-UKI. */ + install_embedded_devicetree(loaded_image, sections, &dt_state); + install_addon_devicetrees(&dt_state, dt_addons, n_dt_addons, ¶meters_measured); + + /* Generate & find all initrds */ + generate_sidecar_initrds(loaded_image, initrds, ¶meters_measured, &sysext_measured, &confext_measured); + generate_embedded_initrds(loaded_image, sections, initrds); + lookup_embedded_initrds(loaded_image, sections, initrds); + + /* Export variables indicating what we measured */ + export_pcr_variables(sections_measured, parameters_measured, sysext_measured, confext_measured); + + /* Combine the initrds into one */ _cleanup_pages_ Pages initrd_pages = {}; - if (ucode_base || credential_initrd || global_credential_initrd || sysext_initrd || confext_initrd || pcrsig_initrd || pcrpkey_initrd) { - /* If we have generated initrds dynamically or there is a microcode initrd, combine them with the built-in initrd. */ - err = combine_initrds( - (const void*const[]) { - /* Microcode must always be first as kernel only scans uncompressed cpios - * and later initrds might be compressed. */ - PHYSICAL_ADDRESS_TO_POINTER(ucode_base), - PHYSICAL_ADDRESS_TO_POINTER(initrd_base), - credential_initrd, - global_credential_initrd, - sysext_initrd, - confext_initrd, - pcrsig_initrd, - pcrpkey_initrd, - }, - (const size_t[]) { - ucode_size, - initrd_size, - credential_initrd_size, - global_credential_initrd_size, - sysext_initrd_size, - confext_initrd_size, - pcrsig_initrd_size, - pcrpkey_initrd_size, - }, - 8, - &initrd_pages, &initrd_size); + struct iovec final_initrd; + if (initrds_need_combine(initrds)) { + /* If we have generated initrds dynamically or there is a microcode initrd, combine them with + * the built-in initrd. */ + err = combine_initrds(initrds, _INITRD_MAX, &initrd_pages, &final_initrd.iov_len); if (err != EFI_SUCCESS) return err; - initrd_base = initrd_pages.addr; + final_initrd.iov_base = PHYSICAL_ADDRESS_TO_POINTER(initrd_pages.addr); - /* Given these might be large let's free them explicitly, quickly. */ - credential_initrd = mfree(credential_initrd); - global_credential_initrd = mfree(global_credential_initrd); - sysext_initrd = mfree(sysext_initrd); - confext_initrd = mfree(confext_initrd); - pcrsig_initrd = mfree(pcrsig_initrd); - pcrpkey_initrd = mfree(pcrpkey_initrd); - } + /* Given these might be large let's free them explicitly before we pass control to Linux */ + initrds_free(&initrds); + } else + final_initrd = initrds[INITRD_BASE]; + + struct iovec kernel = IOVEC_MAKE( + (const uint8_t*) loaded_image->ImageBase + sections[UNIFIED_SECTION_LINUX].memory_offset, + sections[UNIFIED_SECTION_LINUX].size); - err = linux_exec(image, cmdline, - PHYSICAL_ADDRESS_TO_POINTER(linux_base), linux_size, - PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); + err = linux_exec(image, cmdline, &kernel, &final_initrd); graphics_mode(false); return err; } -DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /*wait_for_debugger=*/false); +DEFINE_EFI_MAIN_FUNCTION(run, "systemd-stub", /* wait_for_debugger= */ false); diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index b5c8c6306e..eb29eb2d5b 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -330,7 +330,14 @@ EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) { return EFI_SUCCESS; } -EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **ret, size_t *ret_size) { +EFI_STATUS file_read( + EFI_FILE *dir, + const char16_t *name, + uint64_t off, + size_t size, + char **ret, + size_t *ret_size) { + _cleanup_(file_closep) EFI_FILE *handle = NULL; _cleanup_free_ char *buf = NULL; EFI_STATUS err; @@ -350,6 +357,9 @@ EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t siz if (err != EFI_SUCCESS) return err; + if (info->FileSize > SIZE_MAX) + return EFI_BAD_BUFFER_SIZE; + size = info->FileSize; } diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index ceac07ca39..dc624f45ae 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -102,7 +102,7 @@ char16_t *xstr8_to_path(const char *stra); char16_t *mangle_stub_cmdline(char16_t *cmdline); EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf); -EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, size_t off, size_t size, char **content, size_t *content_size); +EFI_STATUS file_read(EFI_FILE *dir, const char16_t *name, uint64_t off, size_t size, char **content, size_t *content_size); static inline void file_closep(EFI_FILE **handle) { if (!*handle) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 5061afe6c9..4a5fac7606 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -159,6 +159,14 @@ static int acquire_bus(bool set_monitor, sd_bus **ret) { return 0; } +static void notify_bus_error(const sd_bus_error *error) { + + if (!sd_bus_error_is_set(error)) + return; + + (void) sd_notifyf(/* unset_environment= */ false, "BUSERROR=%s", error->name); +} + static int list_bus_names(int argc, char **argv, void *userdata) { _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -459,6 +467,7 @@ static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *p "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, NULL); if (r < 0) { + notify_bus_error(&error); printf("%sFailed to introspect object %s of service %s: %s%s\n", ansi_highlight_red(), path, service, bus_error_message(&error, r), @@ -996,9 +1005,11 @@ static int introspect(int argc, char **argv, void *userdata) { r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply_xml, NULL); - if (r < 0) + if (r < 0) { + notify_bus_error(&error); return log_error_errno(r, "Failed to introspect object %s of service %s: %s", argv[2], argv[1], bus_error_message(&error, r)); + } r = sd_bus_message_read(reply_xml, "s", &xml); if (r < 0) @@ -1032,9 +1043,11 @@ static int introspect(int argc, char **argv, void *userdata) { r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", m->interface); - if (r < 0) + if (r < 0) { + notify_bus_error(&error); return log_error_errno(r, "Failed to get all properties on interface %s: %s", m->interface, bus_error_message(&error, r)); + } r = sd_bus_message_enter_container(reply, 'a', "{sv}"); if (r < 0) @@ -1305,9 +1318,11 @@ static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f return bus_log_create_error(r); r = sd_bus_call(bus, message, arg_timeout, &error, NULL); - if (r < 0) + if (r < 0) { + notify_bus_error(&error); return log_error_errno(r, "Call to org.freedesktop.DBus.Monitoring.BecomeMonitor failed: %s", bus_error_message(&error, r)); + } r = sd_bus_get_unique_name(bus, &unique_name); if (r < 0) @@ -2076,8 +2091,10 @@ static int call(int argc, char **argv, void *userdata) { } r = sd_bus_call(bus, m, arg_timeout, &error, &reply); - if (r < 0) + if (r < 0) { + notify_bus_error(&error); return log_error_errno(r, "Call failed: %s", bus_error_message(&error, r)); + } r = sd_bus_message_is_empty(reply); if (r < 0) @@ -2180,10 +2197,12 @@ static int get_property(int argc, char **argv, void *userdata) { r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Get", &error, &reply, "ss", argv[3], *i); - if (r < 0) + if (r < 0) { + notify_bus_error(&error); return log_error_errno(r, "Failed to get property %s on interface %s: %s", *i, argv[3], bus_error_message(&error, r)); + } r = sd_bus_message_peek_type(reply, &type, &contents); if (r < 0) @@ -2267,10 +2286,12 @@ static int set_property(int argc, char **argv, void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many parameters for signature."); r = sd_bus_call(bus, m, arg_timeout, &error, NULL); - if (r < 0) + if (r < 0) { + notify_bus_error(&error); return log_error_errno(r, "Failed to set property %s on interface %s: %s", argv[4], argv[3], bus_error_message(&error, r)); + } return 0; } diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 530a0f7a0b..df5168caaf 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -58,6 +58,7 @@ static BUS_DEFINE_PROPERTY_GET(property_get_mount_apivfs, "b", ExecContext, exec static BUS_DEFINE_PROPERTY_GET2(property_get_ioprio_class, "i", ExecContext, exec_context_get_effective_ioprio, ioprio_prio_class); static BUS_DEFINE_PROPERTY_GET2(property_get_ioprio_priority, "i", ExecContext, exec_context_get_effective_ioprio, ioprio_prio_data); static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_empty_string, "s", NULL); +static BUS_DEFINE_PROPERTY_GET_REF(property_get_private_tmp_ex, "s", PrivateTmp, private_tmp_to_string); static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_level, "i", int, LOG_PRI); static BUS_DEFINE_PROPERTY_GET_REF(property_get_syslog_facility, "i", int, LOG_FAC); static BUS_DEFINE_PROPERTY_GET(property_get_cpu_affinity_from_numa, "b", ExecContext, exec_context_get_cpu_affinity_from_numa); @@ -482,17 +483,16 @@ static int property_get_bind_paths( if (r < 0) return r; - for (size_t i = 0; i < c->n_bind_mounts; i++) { - - if (ro != c->bind_mounts[i].read_only) + FOREACH_ARRAY(i, c->bind_mounts, c->n_bind_mounts) { + if (ro != i->read_only) continue; r = sd_bus_message_append( reply, "(ssbt)", - c->bind_mounts[i].source, - c->bind_mounts[i].destination, - c->bind_mounts[i].ignore_enoent, - c->bind_mounts[i].recursive ? (uint64_t) MS_REC : UINT64_C(0)); + i->source, + i->destination, + i->ignore_enoent, + i->recursive ? (uint64_t) MS_REC : UINT64_C(0)); if (r < 0) return r; } @@ -520,9 +520,7 @@ static int property_get_temporary_filesystems( if (r < 0) return r; - for (unsigned i = 0; i < c->n_temporary_filesystems; i++) { - TemporaryFileSystem *t = c->temporary_filesystems + i; - + FOREACH_ARRAY(t, c->temporary_filesystems, c->n_temporary_filesystems) { r = sd_bus_message_append( reply, "(ss)", t->path, @@ -554,8 +552,8 @@ static int property_get_log_extra_fields( if (r < 0) return r; - for (size_t i = 0; i < c->n_log_extra_fields; i++) { - r = sd_bus_message_append_array(reply, 'y', c->log_extra_fields[i].iov_base, c->log_extra_fields[i].iov_len); + FOREACH_ARRAY(i, c->log_extra_fields, c->n_log_extra_fields) { + r = sd_bus_message_append_array(reply, 'y', i->iov_base, i->iov_len); if (r < 0) return r; } @@ -777,30 +775,35 @@ static int property_get_mount_images( if (r < 0) return r; - for (size_t i = 0; i < c->n_mount_images; i++) { + FOREACH_ARRAY(i, c->mount_images, c->n_mount_images) { r = sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "ssba(ss)"); if (r < 0) return r; + r = sd_bus_message_append( reply, "ssb", - c->mount_images[i].source, - c->mount_images[i].destination, - c->mount_images[i].ignore_enoent); + i->source, + i->destination, + i->ignore_enoent); if (r < 0) return r; + r = sd_bus_message_open_container(reply, 'a', "(ss)"); if (r < 0) return r; - LIST_FOREACH(mount_options, m, c->mount_images[i].mount_options) { + + LIST_FOREACH(mount_options, m, i->mount_options) { r = sd_bus_message_append(reply, "(ss)", partition_designator_to_string(m->partition_designator), m->options); if (r < 0) return r; } + r = sd_bus_message_close_container(reply); if (r < 0) return r; + r = sd_bus_message_close_container(reply); if (r < 0) return r; @@ -829,29 +832,34 @@ static int property_get_extension_images( if (r < 0) return r; - for (size_t i = 0; i < c->n_extension_images; i++) { + FOREACH_ARRAY(i, c->extension_images, c->n_extension_images) { r = sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "sba(ss)"); if (r < 0) return r; + r = sd_bus_message_append( reply, "sb", - c->extension_images[i].source, - c->extension_images[i].ignore_enoent); + i->source, + i->ignore_enoent); if (r < 0) return r; + r = sd_bus_message_open_container(reply, 'a', "(ss)"); if (r < 0) return r; - LIST_FOREACH(mount_options, m, c->extension_images[i].mount_options) { + + LIST_FOREACH(mount_options, m, i->mount_options) { r = sd_bus_message_append(reply, "(ss)", partition_designator_to_string(m->partition_designator), m->options); if (r < 0) return r; } + r = sd_bus_message_close_container(reply); if (r < 0) return r; + r = sd_bus_message_close_container(reply); if (r < 0) return r; @@ -860,7 +868,7 @@ static int property_get_extension_images( return sd_bus_message_close_container(reply); } -static int bus_property_get_exec_dir( +static int property_get_exec_dir( sd_bus *bus, const char *path, const char *interface, @@ -880,8 +888,8 @@ static int bus_property_get_exec_dir( if (r < 0) return r; - for (size_t i = 0; i < d->n_items; i++) { - r = sd_bus_message_append_basic(reply, 's', d->items[i].path); + FOREACH_ARRAY(i, d->items, d->n_items) { + r = sd_bus_message_append_basic(reply, 's', i->path); if (r < 0) return r; } @@ -889,7 +897,7 @@ static int bus_property_get_exec_dir( return sd_bus_message_close_container(reply); } -static int bus_property_get_exec_dir_symlink( +static int property_get_exec_dir_symlink( sd_bus *bus, const char *path, const char *interface, @@ -909,9 +917,9 @@ static int bus_property_get_exec_dir_symlink( if (r < 0) return r; - for (size_t i = 0; i < d->n_items; i++) - STRV_FOREACH(dst, d->items[i].symlinks) { - r = sd_bus_message_append(reply, "(sst)", d->items[i].path, *dst, UINT64_C(0) /* flags, unused for now */); + FOREACH_ARRAY(i, d->items, d->n_items) + STRV_FOREACH(dst, i->symlinks) { + r = sd_bus_message_append(reply, "(sst)", i->path, *dst, UINT64_C(0) /* flags, unused for now */); if (r < 0) return r; } @@ -943,6 +951,21 @@ static int property_get_image_policy( return sd_bus_message_append(reply, "s", s); } +static int property_get_private_tmp( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + PrivateTmp *p = ASSERT_PTR(userdata); + int b = *p != PRIVATE_TMP_OFF; + + return sd_bus_message_append_basic(reply, 'b', &b); +} + const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1055,7 +1078,8 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("NoExecPaths", "as", NULL, offsetof(ExecContext, no_exec_paths), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ExecSearchPath", "as", NULL, offsetof(ExecContext, exec_search_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MountFlags", "t", bus_property_get_ulong, offsetof(ExecContext, mount_propagation_flag), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PrivateTmp", "b", bus_property_get_private_tmp, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateTmp", "b", property_get_private_tmp, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PrivateTmpEx", "s", property_get_private_tmp_ex, offsetof(ExecContext, private_tmp), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ProtectClock", "b", bus_property_get_bool, offsetof(ExecContext, protect_clock), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ProtectKernelTunables", "b", bus_property_get_bool, offsetof(ExecContext, protect_kernel_tunables), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1083,21 +1107,21 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("Personality", "s", property_get_personality, offsetof(ExecContext, personality), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", bus_property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RuntimeDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RuntimeDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("StateDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE].mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StateDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CacheDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StateDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CacheDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CacheDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE].mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CacheDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LogsDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CacheDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LogsDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogsDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LogsDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LogsDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ConfigurationDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION].mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConfigurationDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConfigurationDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("TimeoutCleanUSec", "t", bus_property_get_usec, offsetof(ExecContext, timeout_clean_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1735,8 +1759,40 @@ int bus_exec_context_set_transient_property( if (streq(name, "TTYColumns")) return bus_set_transient_unsigned(u, name, &c->tty_cols, message, flags, error); - if (streq(name, "PrivateTmp")) - return bus_set_transient_private_tmp(u, name, &c->private_tmp, message, flags, error); + if (streq(name, "PrivateTmp")) { + int v; + + r = sd_bus_message_read(message, "b", &v); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + c->private_tmp = v ? PRIVATE_TMP_CONNECTED : PRIVATE_TMP_OFF; + (void) unit_write_settingf(u, flags, name, "%s=%s", name, yes_no(v)); + } + + return 1; + + } else if (streq(name, "PrivateTmpEx")) { + const char *s; + PrivateTmp t; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + t = private_tmp_from_string(s); + if (t < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s setting: %s", name, s); + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + c->private_tmp = t; + (void) unit_write_settingf(u, flags, name, "PrivateTmp=%s", + private_tmp_to_string(c->private_tmp)); + } + + return 1; + } if (streq(name, "PrivateDevices")) return bus_set_transient_bool(u, name, &c->private_devices, message, flags, error); @@ -2743,10 +2799,6 @@ int bus_exec_context_set_transient_property( if (!path_is_normalized(simplified)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "WorkingDirectory= expects a normalized path or '~'"); - - if (path_below_api_vfs(simplified)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, - "WorkingDirectory= may not be below /proc/, /sys/ or /dev/"); } } diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index ff970df957..cf7e2fe0eb 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -351,6 +351,8 @@ const sd_bus_vtable bus_service_vtable[] = { SD_BUS_PROPERTY("FileDescriptorStorePreserve", "s", bus_property_get_exec_preserve_mode, offsetof(Service, fd_store_preserve_mode), 0), SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("StatusErrno", "i", bus_property_get_int, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("StatusBusError", "s", NULL, offsetof(Service, status_bus_error), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("StatusVarlinkError", "s", NULL, offsetof(Service, status_varlink_error), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("ReloadResult", "s", property_get_result, offsetof(Service, reload_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("CleanResult", "s", property_get_result, offsetof(Service, clean_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), diff --git a/src/core/dbus-util.c b/src/core/dbus-util.c index 46676d7a21..b871d89368 100644 --- a/src/core/dbus-util.c +++ b/src/core/dbus-util.c @@ -150,45 +150,6 @@ int bus_set_transient_usec_internal( return 1; } -int bus_set_transient_private_tmp( - Unit *u, - const char *name, - PrivateTmp *p, - sd_bus_message *message, - UnitWriteFlags flags, - sd_bus_error *error) { - - int v, r; - - assert(p); - - r = sd_bus_message_read(message, "b", &v); - if (r < 0) - return r; - - if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - *p = v ? PRIVATE_TMP_CONNECTED : PRIVATE_TMP_OFF; - unit_write_settingf(u, flags, name, "%s=%s", name, yes_no(v)); - } - - return 1; -} - -int bus_property_get_private_tmp( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - PrivateTmp *p = ASSERT_PTR(userdata); - int b = *p != PRIVATE_TMP_OFF; - - return sd_bus_message_append_basic(reply, 'b', &b); -} - int bus_verify_manage_units_async_full( Unit *u, const char *verb, diff --git a/src/core/dbus-util.h b/src/core/dbus-util.h index 29796eb249..0fc3a94961 100644 --- a/src/core/dbus-util.h +++ b/src/core/dbus-util.h @@ -4,7 +4,6 @@ #include "sd-bus.h" #include "dissect-image.h" -#include "execute.h" #include "unit.h" int bus_property_get_triggered_unit(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); @@ -245,7 +244,6 @@ int bus_set_transient_string(Unit *u, const char *name, char **p, sd_bus_message int bus_set_transient_bool(Unit *u, const char *name, bool *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); int bus_set_transient_tristate(Unit *u, const char *name, int *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); int bus_set_transient_usec_internal(Unit *u, const char *name, usec_t *p, bool fix_0, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); -int bus_set_transient_private_tmp(Unit *u, const char *name, PrivateTmp *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); static inline int bus_set_transient_usec(Unit *u, const char *name, usec_t *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error) { return bus_set_transient_usec_internal(u, name, p, false, message, flags, error); } @@ -257,4 +255,3 @@ int bus_verify_manage_units_async_full(Unit *u, const char *verb, const char *po int bus_read_mount_options(sd_bus_message *message, sd_bus_error *error, MountOptions **ret_options, char **ret_format_str, const char *separator); int bus_property_get_activation_details(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); -int bus_property_get_private_tmp(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 8b88ccb1e9..94c908afd7 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include <linux/sched.h> #include <sys/eventfd.h> #include <sys/ioctl.h> #include <sys/mount.h> @@ -44,6 +45,7 @@ #include "journal-send.h" #include "missing_ioprio.h" #include "missing_prctl.h" +#include "missing_sched.h" #include "missing_securebits.h" #include "missing_syscall.h" #include "mkdir-label.h" @@ -1439,6 +1441,13 @@ static int apply_syscall_filter(const ExecContext *c, const ExecParameters *p, b return r; } + /* Sending over exec_fd or handoff_timestamp_fd requires write() syscall. */ + if (p->exec_fd >= 0 || p->handoff_timestamp_fd >= 0) { + r = seccomp_filter_set_add_by_name(c->syscall_filter, c->syscall_allow_list, "write"); + if (r < 0) + return r; + } + return seccomp_load_syscall_filter_set_raw(default_action, c->syscall_filter, action, false); } @@ -3205,8 +3214,6 @@ static int apply_mount_namespace( .temporary_filesystems = context->temporary_filesystems, .n_temporary_filesystems = context->n_temporary_filesystems, - .private_tmp = context->private_tmp, - .mount_images = context->mount_images, .n_mount_images = context->n_mount_images, .mount_image_policy = context->mount_image_policy ?: &image_policy_service, @@ -3245,6 +3252,7 @@ static int apply_mount_namespace( .private_dev = needs_sandboxing && context->private_devices, .private_network = needs_sandboxing && exec_needs_network_namespace(context), .private_ipc = needs_sandboxing && exec_needs_ipc_namespace(context), + .private_tmp = needs_sandboxing ? context->private_tmp : false, .mount_apivfs = needs_sandboxing && exec_context_get_effective_mount_apivfs(context), @@ -3718,7 +3726,7 @@ static int connect_unix_harder(const ExecContext *c, const ExecParameters *p, co r = sockaddr_un_set_path(&addr.un, FORMAT_PROC_FD_PATH(ofd)); if (r < 0) - return log_exec_error_errno(c, p, r, "Failed to set sockaddr for '%s': %m", of->path); + return log_exec_debug_errno(c, p, r, "Failed to set sockaddr for '%s': %m", of->path); sa_len = r; FOREACH_ELEMENT(i, socket_types) { @@ -3726,7 +3734,7 @@ static int connect_unix_harder(const ExecContext *c, const ExecParameters *p, co fd = socket(AF_UNIX, *i|SOCK_CLOEXEC, 0); if (fd < 0) - return log_exec_error_errno(c, p, + return log_exec_debug_errno(c, p, errno, "Failed to create socket for '%s': %m", of->path); @@ -3734,12 +3742,12 @@ static int connect_unix_harder(const ExecContext *c, const ExecParameters *p, co if (r >= 0) return TAKE_FD(fd); if (r != -EPROTOTYPE) - return log_exec_error_errno(c, p, + return log_exec_debug_errno(c, p, r, "Failed to connect to socket for '%s': %m", of->path); } - return log_exec_error_errno(c, p, + return log_exec_debug_errno(c, p, SYNTHETIC_ERRNO(EPROTOTYPE), "No suitable socket type to connect to socket '%s'.", of->path); } @@ -3754,10 +3762,10 @@ static int get_open_file_fd(const ExecContext *c, const ExecParameters *p, const ofd = open(of->path, O_PATH | O_CLOEXEC); if (ofd < 0) - return log_exec_error_errno(c, p, errno, "Failed to open '%s' as O_PATH: %m", of->path); + return log_exec_debug_errno(c, p, errno, "Failed to open '%s' as O_PATH: %m", of->path); if (fstat(ofd, &st) < 0) - return log_exec_error_errno(c, p, errno, "Failed to stat '%s': %m", of->path); + return log_exec_debug_errno(c, p, errno, "Failed to stat '%s': %m", of->path); if (S_ISSOCK(st.st_mode)) { fd = connect_unix_harder(c, p, of, ofd); @@ -3765,7 +3773,7 @@ static int get_open_file_fd(const ExecContext *c, const ExecParameters *p, const return fd; if (FLAGS_SET(of->flags, OPENFILE_READ_ONLY) && shutdown(fd, SHUT_WR) < 0) - return log_exec_error_errno(c, p, + return log_exec_debug_errno(c, p, errno, "Failed to shutdown send for socket '%s': %m", of->path); @@ -3777,9 +3785,9 @@ static int get_open_file_fd(const ExecContext *c, const ExecParameters *p, const else if (FLAGS_SET(of->flags, OPENFILE_TRUNCATE)) flags |= O_TRUNC; - fd = fd_reopen(ofd, flags | O_CLOEXEC); + fd = fd_reopen(ofd, flags|O_NOCTTY|O_CLOEXEC); if (fd < 0) - return log_exec_error_errno(c, p, fd, "Failed to reopen file '%s': %m", of->path); + return log_exec_debug_errno(c, p, fd, "Failed to reopen file '%s': %m", of->path); log_exec_debug(c, p, "Opened file '%s' as fd %d.", of->path, fd); } @@ -3788,8 +3796,6 @@ static int get_open_file_fd(const ExecContext *c, const ExecParameters *p, const } static int collect_open_file_fds(const ExecContext *c, ExecParameters *p, size_t *n_fds) { - int r; - assert(c); assert(p); assert(n_fds); @@ -3800,21 +3806,24 @@ static int collect_open_file_fds(const ExecContext *c, ExecParameters *p, size_t fd = get_open_file_fd(c, p, of); if (fd < 0) { if (FLAGS_SET(of->flags, OPENFILE_GRACEFUL)) { - log_exec_warning_errno(c, p, fd, - "Failed to get OpenFile= file descriptor for '%s', ignoring: %m", - of->path); + log_exec_full_errno(c, p, + fd == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(fd) ? LOG_DEBUG : LOG_WARNING, + fd, + "Failed to get OpenFile= file descriptor for '%s', ignoring: %m", + of->path); continue; } - return fd; + return log_exec_error_errno(c, p, fd, + "Failed to get OpenFile= file descriptor for '%s': %m", + of->path); } if (!GREEDY_REALLOC(p->fds, *n_fds + 1)) - return -ENOMEM; + return log_oom(); - r = strv_extend(&p->fd_names, of->fdname); - if (r < 0) - return r; + if (strv_extend(&p->fd_names, of->fdname) < 0) + return log_oom(); p->fds[(*n_fds)++] = TAKE_FD(fd); } @@ -4013,7 +4022,7 @@ static int send_handoff_timestamp( dual_timestamp dt; dual_timestamp_now(&dt); - if (send(p->handoff_timestamp_fd, (const usec_t[2]) { dt.realtime, dt.monotonic }, sizeof(usec_t) * 2, 0) < 0) { + if (write(p->handoff_timestamp_fd, (const usec_t[2]) { dt.realtime, dt.monotonic }, sizeof(usec_t) * 2) < 0) { if (reterr_exit_status) *reterr_exit_status = EXIT_EXEC; return log_exec_error_errno(c, p, errno, "Failed to send handoff timestamp: %m"); @@ -4404,15 +4413,14 @@ int exec_invoke( } if (context->cpu_sched_set) { - struct sched_param param = { + struct sched_attr attr = { + .size = sizeof(attr), + .sched_policy = context->cpu_sched_policy, .sched_priority = context->cpu_sched_priority, + .sched_flags = context->cpu_sched_reset_on_fork ? SCHED_FLAG_RESET_ON_FORK : 0, }; - r = sched_setscheduler(0, - context->cpu_sched_policy | - (context->cpu_sched_reset_on_fork ? - SCHED_RESET_ON_FORK : 0), - ¶m); + r = sched_setattr(/* pid= */ 0, &attr, /* flags= */ 0); if (r < 0) { *exit_status = EXIT_SETSCHEDULER; return log_exec_error_errno(context, params, errno, "Failed to set up CPU scheduling: %m"); diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 46c2198ac7..e04450d869 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -119,7 +119,7 @@ {{type}}.BindPaths, config_parse_bind_paths, 0, offsetof({{type}}, exec_context) {{type}}.BindReadOnlyPaths, config_parse_bind_paths, 0, offsetof({{type}}, exec_context) {{type}}.TemporaryFileSystem, config_parse_temporary_filesystems, 0, offsetof({{type}}, exec_context) -{{type}}.PrivateTmp, config_parse_private_tmp, 0, offsetof({{type}}, exec_context) +{{type}}.PrivateTmp, config_parse_private_tmp, 0, offsetof({{type}}, exec_context.private_tmp) {{type}}.PrivateDevices, config_parse_bool, 0, offsetof({{type}}, exec_context.private_devices) {{type}}.ProtectKernelTunables, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_kernel_tunables) {{type}}.ProtectKernelModules, config_parse_bool, 0, offsetof({{type}}, exec_context.protect_kernel_modules) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 3701270ab5..e2a528a629 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -133,6 +133,7 @@ DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGrou DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode, "Failed to parse keyring mode"); DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_proc, protect_proc, ProtectProc, "Failed to parse /proc/ protection mode"); DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset, "Failed to parse /proc/ subset mode"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_private_tmp, private_tmp, PrivateTmp, "Failed to parse private tmp value"); DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode, "Failed to parse utmp mode"); DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to parse job mode"); DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); @@ -2634,7 +2635,8 @@ int config_parse_working_directory( return missing_ok ? 0 : -ENOEXEC; } - r = path_simplify_and_warn(k, PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS|(missing_ok ? 0 : PATH_CHECK_FATAL), unit, filename, line, lvalue); + r = path_simplify_and_warn(k, PATH_CHECK_ABSOLUTE|(missing_ok ? 0 : PATH_CHECK_FATAL), + unit, filename, line, lvalue); if (r < 0) return missing_ok ? 0 : -ENOEXEC; @@ -3698,15 +3700,14 @@ int config_parse_unit_slice( void *data, void *userdata) { + Unit *u = ASSERT_PTR(userdata), *slice; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *k = NULL; - Unit *u = userdata, *slice; int r; assert(filename); assert(lvalue); assert(rvalue); - assert(u); r = unit_name_printf(u, rvalue, &k); if (r < 0) { @@ -3907,8 +3908,8 @@ int config_parse_tasks_max( void *data, void *userdata) { - const Unit *u = userdata; - CGroupTasksMax *tasks_max = data; + CGroupTasksMax *tasks_max = ASSERT_PTR(data); + const Unit *u = ASSERT_PTR(userdata); uint64_t v; int r; @@ -5199,34 +5200,6 @@ int config_parse_temporary_filesystems( } } -int config_parse_private_tmp( - 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) { - - ExecContext *c = ASSERT_PTR(data); - int r; - - assert(filename); - assert(rvalue); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse boolean value: %s ignoring", rvalue); - return 0; - } - - c->private_tmp = r ? PRIVATE_TMP_CONNECTED : PRIVATE_TMP_OFF; - return 0; -} - int config_parse_bind_paths( const char *unit, const char *filename, diff --git a/src/core/namespace.c b/src/core/namespace.c index 0a1d20b5bb..6d3cadf05c 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -2314,7 +2314,10 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) { .source_dir_mode = 01777, .create_source_dir = true, }; - } else { + + } else if (p->tmp_dir || p->var_tmp_dir) { + assert(p->private_tmp == PRIVATE_TMP_CONNECTED); + if (p->tmp_dir) { bool ro = streq(p->tmp_dir, RUN_SYSTEMD_EMPTY); @@ -3154,4 +3157,4 @@ static const char* const private_tmp_table[_PRIVATE_TMP_MAX] = { [PRIVATE_TMP_DISCONNECTED] = "disconnected", }; -DEFINE_STRING_TABLE_LOOKUP(private_tmp, PrivateTmp); +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(private_tmp, PrivateTmp, PRIVATE_TMP_CONNECTED); diff --git a/src/core/service.c b/src/core/service.c index f37a941a6d..937729cd06 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -468,6 +468,8 @@ static void service_done(Unit *u) { s->pid_file = mfree(s->pid_file); s->status_text = mfree(s->status_text); + s->status_bus_error = mfree(s->status_bus_error); + s->status_varlink_error = mfree(s->status_varlink_error); s->exec_runtime = exec_runtime_free(s->exec_runtime); @@ -1045,6 +1047,14 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { fprintf(f, "%sStatus Errno: %s\n", prefix, STRERROR(s->status_errno)); + if (s->status_bus_error) + fprintf(f, "%sStatus Bus Error: %s\n", + prefix, s->status_bus_error); + + if (s->status_varlink_error) + fprintf(f, "%sStatus Varlink Error: %s\n", + prefix, s->status_varlink_error); + if (s->n_fd_store_max > 0) fprintf(f, "%sFile Descriptor Store Max: %u\n" @@ -2765,6 +2775,8 @@ static int service_start(Unit *u) { s->status_text = mfree(s->status_text); s->status_errno = 0; + s->status_bus_error = mfree(s->status_bus_error); + s->status_varlink_error = mfree(s->status_varlink_error); s->notify_access_override = _NOTIFY_ACCESS_INVALID; s->notify_state = NOTIFY_UNKNOWN; @@ -3036,6 +3048,8 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { return r; (void) serialize_item_format(f, "status-errno", "%d", s->status_errno); + (void) serialize_item(f, "status-bus-error", s->status_bus_error); + (void) serialize_item(f, "status-varlink-error", s->status_varlink_error); (void) serialize_dual_timestamp(f, "watchdog-timestamp", &s->watchdog_timestamp); @@ -3370,6 +3384,14 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, else s->status_errno = i; + } else if (streq(key, "status-bus-error")) { + if (free_and_strdup(&s->status_bus_error, value) < 0) + log_oom_debug(); + + } else if (streq(key, "status-varlink-error")) { + if (free_and_strdup(&s->status_varlink_error, value) < 0) + log_oom_debug(); + } else if (streq(key, "watchdog-timestamp")) (void) deserialize_dual_timestamp(value, &s->watchdog_timestamp); else if (streq(key, "watchdog-original-usec")) @@ -4353,7 +4375,7 @@ static void service_notify_message( if (DEBUG_LOGGING) { _cleanup_free_ char *cc = strv_join(tags, ", "); - log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", ucred->pid, empty_to_na(cc)); + log_unit_debug(u, "Got notification message from PID "PID_FMT": %s", ucred->pid, empty_to_na(cc)); } usec_t monotonic_usec = USEC_INFINITY; @@ -4479,7 +4501,7 @@ static void service_notify_message( else { t = strdup(e); if (!t) - log_oom(); + log_oom_warning(); } } @@ -4523,10 +4545,35 @@ static void service_notify_message( } } + static const struct { + const char *tag; + size_t status_offset; + } status_errors[] = { + { "BUSERROR=", offsetof(Service, status_bus_error) }, + { "VARLINKERROR=", offsetof(Service, status_varlink_error) }, + }; + + FOREACH_ELEMENT(i, status_errors) { + e = strv_find_startswith(tags, i->tag); + if (!e) + continue; + + char **status_error = (char**) ((uint8_t*) s + i->status_offset); + + e = empty_to_null(e); + + if (e && !string_is_safe_ascii(e)) { + _cleanup_free_ char *escaped = cescape(e); + log_unit_warning(u, "Got invalid %s string, ignoring: %s", i->tag, strna(escaped)); + } else if (free_and_strdup_warn(status_error, e) > 0) + notify_dbus = true; + } + /* Interpret EXTEND_TIMEOUT= */ e = strv_find_startswith(tags, "EXTEND_TIMEOUT_USEC="); if (e) { usec_t extend_timeout_usec; + if (safe_atou64(e, &extend_timeout_usec) < 0) log_unit_warning(u, "Failed to parse EXTEND_TIMEOUT_USEC=%s", e); else diff --git a/src/core/service.h b/src/core/service.h index 55ea413f40..1d67d13fda 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -198,6 +198,8 @@ struct Service { char *bus_name; char *status_text; + char *status_bus_error; + char *status_varlink_error; int status_errno; sd_event_source *timer_event_source; diff --git a/src/core/unit.c b/src/core/unit.c index e8b32e862d..0e931be484 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -41,6 +41,7 @@ #include "logarithm.h" #include "macro.h" #include "mkdir-label.h" +#include "mountpoint-util.h" #include "path-util.h" #include "process-util.h" #include "rm-rf.h" @@ -4224,6 +4225,10 @@ static int unit_verify_contexts(const Unit *u, const ExecContext *ec) { if (ec->dynamic_user && ec->working_directory_home) return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "WorkingDirectory=~ is not allowed under DynamicUser=yes. Refusing."); + if (ec->working_directory && path_below_api_vfs(ec->working_directory) && + exec_needs_mount_namespace(ec, /* params = */ NULL, /* runtime = */ NULL)) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "WorkingDirectory= may not be below /proc/, /sys/ or /dev/ when using mount namespacing. Refusing."); + return 0; } diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 89986bad95..8e53b9bb47 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -3,10 +3,14 @@ #include "ask-password-api.h" #include "cryptenroll-fido2.h" #include "cryptsetup-fido2.h" +#include "fido2-util.h" +#include "glyph-util.h" #include "hexdecoct.h" +#include "iovec-util.h" #include "json-util.h" #include "libfido2-util.h" #include "memory-util.h" +#include "pretty-print.h" #include "random-util.h" int load_volume_key_fido2( @@ -67,13 +71,16 @@ int enroll_fido2( size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, - int cred_alg) { + int cred_alg, + const char *salt_file, + bool parameters_in_header) { - _cleanup_(erase_and_freep) void *salt = NULL, *secret = NULL; + _cleanup_(iovec_done_erase) struct iovec salt = {}; + _cleanup_(erase_and_freep) void *secret = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_free_ char *keyslot_as_string = NULL; - size_t cid_size, salt_size, secret_size; + size_t cid_size, secret_size; _cleanup_free_ void *cid = NULL; ssize_t base64_encoded_size; const char *node, *un; @@ -88,6 +95,18 @@ int enroll_fido2( un = strempty(crypt_get_uuid(cd)); + if (salt_file) + r = fido2_read_salt_file( + salt_file, + /* offset= */ UINT64_MAX, + /* client= */ "cryptenroll", + /* node= */ un, + &salt); + else + r = fido2_generate_salt(&salt); + if (r < 0) + return r; + r = fido2_generate_hmac_hash( device, /* rp_id= */ "io.systemd.cryptsetup", @@ -100,8 +119,8 @@ int enroll_fido2( /* askpw_credential= */ "cryptenroll.fido2-pin", lock_with, cred_alg, + &salt, &cid, &cid_size, - &salt, &salt_size, &secret, &secret_size, NULL, &lock_with); @@ -127,24 +146,61 @@ int enroll_fido2( if (keyslot < 0) return log_error_errno(keyslot, "Failed to add new FIDO2 key to %s: %m", node); - if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) - return log_oom(); - - r = sd_json_buildo(&v, - SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-fido2")), - SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))), - SD_JSON_BUILD_PAIR("fido2-credential", SD_JSON_BUILD_BASE64(cid, cid_size)), - SD_JSON_BUILD_PAIR("fido2-salt", SD_JSON_BUILD_BASE64(salt, salt_size)), - SD_JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_CONST_STRING("io.systemd.cryptsetup")), - SD_JSON_BUILD_PAIR("fido2-clientPin-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))), - SD_JSON_BUILD_PAIR("fido2-up-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))), - SD_JSON_BUILD_PAIR("fido2-uv-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV)))); - if (r < 0) - return log_error_errno(r, "Failed to prepare FIDO2 JSON token object: %m"); - - r = cryptsetup_add_token_json(cd, v); - if (r < 0) - return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m"); + if (parameters_in_header) { + if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) + return log_oom(); + + r = sd_json_buildo(&v, + SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-fido2")), + SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))), + SD_JSON_BUILD_PAIR("fido2-credential", SD_JSON_BUILD_BASE64(cid, cid_size)), + SD_JSON_BUILD_PAIR("fido2-salt", JSON_BUILD_IOVEC_BASE64(&salt)), + SD_JSON_BUILD_PAIR("fido2-rp", JSON_BUILD_CONST_STRING("io.systemd.cryptsetup")), + SD_JSON_BUILD_PAIR("fido2-clientPin-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_PIN))), + SD_JSON_BUILD_PAIR("fido2-up-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))), + SD_JSON_BUILD_PAIR("fido2-uv-required", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV)))); + if (r < 0) + return log_error_errno(r, "Failed to prepare FIDO2 JSON token object: %m"); + + r = cryptsetup_add_token_json(cd, v); + if (r < 0) + return log_error_errno(r, "Failed to add FIDO2 JSON token to LUKS2 header: %m"); + } else { + _cleanup_free_ char *base64_encoded_cid = NULL, *link = NULL; + + r = base64mem(cid, cid_size, &base64_encoded_cid); + if (r < 0) + return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m"); + + r = terminal_urlify_man("crypttab", "5", &link); + if (r < 0) + return log_oom(); + + fflush(stdout); + fprintf(stderr, + "A FIDO2 credential has been registered for this volume:\n\n" + " %s%sfido2-cid=%s", + emoji_enabled() ? special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY) : "", + emoji_enabled() ? " " : "", + ansi_highlight()); + fflush(stderr); + + fputs(base64_encoded_cid, stdout); + fflush(stdout); + + fputs(ansi_normal(), stderr); + fflush(stderr); + + fputc('\n', stdout); + fflush(stdout); + + fprintf(stderr, + "\nPlease save this FIDO2 credential ID. It is required when unloocking the volume\n" + "using the associated FIDO2 keyslot which we just created. To configure automatic\n" + "unlocking using this FIDO2 token, add an appropriate entry to your /etc/crypttab\n" + "file, see %s for details.\n", link); + fflush(stderr); + } log_info("New FIDO2 token enrolled as key slot %i.", keyslot); return keyslot; diff --git a/src/cryptenroll/cryptenroll-fido2.h b/src/cryptenroll/cryptenroll-fido2.h index 3315308e4d..5cb3bf6bfa 100644 --- a/src/cryptenroll/cryptenroll-fido2.h +++ b/src/cryptenroll/cryptenroll-fido2.h @@ -9,7 +9,7 @@ #if HAVE_LIBFIDO2 int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks); -int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg); +int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header); #else static inline int load_volume_key_fido2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) { @@ -17,7 +17,7 @@ static inline int load_volume_key_fido2(struct crypt_device *cd, const char *cd_ "FIDO2 unlocking not supported."); } -static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg) { +static inline int enroll_fido2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Fido2EnrollFlags lock_with, int cred_alg, const char *salt_file, bool parameters_in_header) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 key enrollment not supported."); } diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 04352bfec6..263b8921b1 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -39,6 +39,8 @@ static char *arg_unlock_fido2_device = NULL; static char *arg_unlock_tpm2_device = NULL; static char *arg_pkcs11_token_uri = NULL; static char *arg_fido2_device = NULL; +static char *arg_fido2_salt_file = NULL; +static bool arg_fido2_parameters_in_header = true; static char *arg_tpm2_device = NULL; static uint32_t arg_tpm2_seal_key_handle = 0; static char *arg_tpm2_device_key = NULL; @@ -69,6 +71,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_unlock_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_fido2_salt_file, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep); @@ -194,6 +197,10 @@ static int help(void) { "\n%3$sFIDO2 Enrollment:%4$s\n" " --fido2-device=PATH\n" " Enroll a FIDO2-HMAC security token\n" + " --fido2-salt-file=PATH\n" + " Use salt from a file instead of generating one\n" + " --fido2-parameters-in-header=BOOL\n" + " Whether to store FIDO2 parameters in the LUKS2 header\n" " --fido2-credential-algorithm=STRING\n" " Specify COSE algorithm for FIDO2 credential\n" " --fido2-with-client-pin=BOOL\n" @@ -243,6 +250,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_UNLOCK_TPM2_DEVICE, ARG_PKCS11_TOKEN_URI, ARG_FIDO2_DEVICE, + ARG_FIDO2_SALT_FILE, + ARG_FIDO2_PARAMETERS_IN_HEADER, ARG_TPM2_DEVICE, ARG_TPM2_DEVICE_KEY, ARG_TPM2_SEAL_KEY_HANDLE, @@ -260,29 +269,31 @@ static int parse_argv(int argc, char *argv[]) { }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "password", no_argument, NULL, ARG_PASSWORD }, - { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, - { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE }, - { "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE }, - { "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE }, - { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, - { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, - { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, - { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN }, - { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, - { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, - { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE }, - { "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK }, - { "tpm2-with-pin", required_argument, NULL, ARG_TPM2_WITH_PIN }, - { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "password", no_argument, NULL, ARG_PASSWORD }, + { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, + { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE }, + { "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE }, + { "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE }, + { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, + { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, + { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, + { "fido2-salt-file", required_argument, NULL, ARG_FIDO2_SALT_FILE }, + { "fido2-parameters-in-header", required_argument, NULL, ARG_FIDO2_PARAMETERS_IN_HEADER }, + { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN }, + { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, + { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, + { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, + { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, + { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, + { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, + { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE }, + { "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK }, + { "tpm2-with-pin", required_argument, NULL, ARG_TPM2_WITH_PIN }, + { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, {} }; @@ -449,6 +460,20 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_FIDO2_SALT_FILE: + r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_fido2_salt_file); + if (r < 0) + return r; + + break; + + case ARG_FIDO2_PARAMETERS_IN_HEADER: + r = parse_boolean_argument("--fido2-parameters-in-header=", optarg, &arg_fido2_parameters_in_header); + if (r < 0) + return r; + + break; + case ARG_TPM2_DEVICE: { _cleanup_free_ char *device = NULL; @@ -630,6 +655,10 @@ static int parse_argv(int argc, char *argv[]) { "When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. " "Please specify device paths for enrolling and unlocking respectively."); + if (!arg_fido2_parameters_in_header && !arg_fido2_salt_file) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "FIDO2 parameters' storage in the LUKS2 header was disabled, but no salt file provided, refusing."); + if (!arg_fido2_device) { r = fido2_find_device_auto(&arg_fido2_device); if (r < 0) @@ -841,7 +870,7 @@ static int run(int argc, char *argv[]) { break; case ENROLL_FIDO2: - slot = enroll_fido2(cd, vk, vks, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg); + slot = enroll_fido2(cd, vk, vks, arg_fido2_device, arg_fido2_lock_with, arg_fido2_cred_alg, arg_fido2_salt_file, arg_fido2_parameters_in_header); break; case ENROLL_TPM2: diff --git a/src/cryptsetup/cryptsetup-keyfile.c b/src/cryptsetup/cryptsetup-keyfile.c index 1867e9012c..e14d0bbda2 100644 --- a/src/cryptsetup/cryptsetup-keyfile.c +++ b/src/cryptsetup/cryptsetup-keyfile.c @@ -5,18 +5,12 @@ #include "path-util.h" #include "strv.h" -int find_key_file( - const char *key_file, - char **search_path, - const char *bindname, - void **ret_key, - size_t *ret_key_size) { +int find_key_file(const char *key_file, char **search_path, const char *bindname, struct iovec *ret_key) { int r; assert(key_file); assert(ret_key); - assert(ret_key_size); if (strv_isempty(search_path) || path_is_absolute(key_file)) { @@ -24,7 +18,7 @@ int find_key_file( AT_FDCWD, key_file, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, bindname, - (char**) ret_key, ret_key_size); + (char**) &ret_key->iov_base, &ret_key->iov_len); if (r == -E2BIG) return log_error_errno(r, "Key file '%s' too large.", key_file); if (r < 0) @@ -44,7 +38,7 @@ int find_key_file( AT_FDCWD, joined, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, bindname, - (char**) ret_key, ret_key_size); + (char**) &ret_key->iov_base, &ret_key->iov_len); if (r >= 0) return 1; if (r == -E2BIG) { @@ -56,7 +50,6 @@ int find_key_file( } /* Search path supplied, but file not found, report by returning NULL, but not failing */ - *ret_key = NULL; - *ret_key_size = 0; + *ret_key = IOVEC_MAKE(NULL, 0); return 0; } diff --git a/src/cryptsetup/cryptsetup-keyfile.h b/src/cryptsetup/cryptsetup-keyfile.h index 83bd1fbed2..fe54e904b0 100644 --- a/src/cryptsetup/cryptsetup-keyfile.h +++ b/src/cryptsetup/cryptsetup-keyfile.h @@ -4,9 +4,6 @@ #include <inttypes.h> #include <sys/types.h> -int find_key_file( - const char *key_file, - char **search_path, - const char *bindname, - void **ret_key, - size_t *ret_key_size); +#include "iovec-util.h" + +int find_key_file(const char *key_file, char **search_path, const char *bindname, struct iovec *ret_key); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index d7fb08fd58..8fec7111f4 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -223,7 +223,7 @@ _public_ void cryptsetup_token_dump( crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str); crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str)); crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg))); - crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); + crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK)); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 0f655661a6..4eec4c450a 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -62,6 +62,14 @@ typedef enum PassphraseType { _PASSPHRASE_TYPE_INVALID = -1, } PassphraseType; +typedef enum TokenType { + TOKEN_TPM2, + TOKEN_FIDO2, + TOKEN_PKCS11, + _TOKEN_TYPE_MAX, + _TOKEN_TYPE_INVALID = -EINVAL, +} TokenType; + static const char *arg_type = NULL; /* ANY_LUKS, CRYPT_LUKS1, CRYPT_LUKS2, CRYPT_TCRYPT, CRYPT_BITLK or CRYPT_PLAIN */ static char *arg_cipher = NULL; static unsigned arg_key_size = 0; @@ -76,7 +84,8 @@ static char *arg_header = NULL; static unsigned arg_tries = 3; static bool arg_readonly = false; static bool arg_verify = false; -static AskPasswordFlags arg_ask_password_flags = 0; +static bool arg_password_cache_set = false; /* Not the actual argument value, just an indicator that some value is set */ +static AskPasswordFlags arg_ask_password_flags = ASK_PASSWORD_ACCEPT_CACHED | ASK_PASSWORD_PUSH_CACHE; static bool arg_discards = false; static bool arg_same_cpu_crypt = false; static bool arg_submit_from_crypt_cpus = false; @@ -131,15 +140,20 @@ STATIC_DESTRUCTOR_REGISTER(arg_link_key_type, freep); STATIC_DESTRUCTOR_REGISTER(arg_link_key_description, freep); static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = { - [PASSPHRASE_REGULAR] = "passphrase", + [PASSPHRASE_REGULAR] = "passphrase", [PASSPHRASE_RECOVERY_KEY] = "recovery key", - [PASSPHRASE_BOTH] = "passphrase or recovery key", + [PASSPHRASE_BOTH] = "passphrase or recovery key", }; -const char* passphrase_type_to_string(PassphraseType t); -PassphraseType passphrase_type_from_string(const char *s); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(passphrase_type, PassphraseType); + +static const char* const token_type_table[_TOKEN_TYPE_MAX] = { + [TOKEN_TPM2] = "tpm2", + [TOKEN_FIDO2] = "fido2", + [TOKEN_PKCS11] = "pkcs11", +}; -DEFINE_STRING_TABLE_LOOKUP(passphrase_type, PassphraseType); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(token_type, TokenType); /* Options Debian's crypttab knows we don't: check= @@ -286,6 +300,21 @@ static int parse_one_option(const char *option) { SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_ECHO, r); SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_SILENT, !r); } + } else if ((val = startswith(option, "password-cache="))) { + arg_password_cache_set = true; + + if (streq(val, "read-only")) { + arg_ask_password_flags |= ASK_PASSWORD_ACCEPT_CACHED; + arg_ask_password_flags &= ~ASK_PASSWORD_PUSH_CACHE; + } else { + r = parse_boolean(val); + if (r < 0) { + log_warning_errno(r, "Invalid password-cache= option \"%s\", ignoring.", val); + return 0; + } + + SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, r); + } } else if (STR_IN_SET(option, "allow-discards", "discard")) arg_discards = true; else if (streq(option, "same-cpu-crypt")) @@ -554,14 +583,15 @@ static int parse_one_option(const char *option) { } else if ((val = startswith(option, "link-volume-key="))) { #ifdef HAVE_CRYPT_SET_KEYRING_TO_LINK - const char *sep, *c; _cleanup_free_ char *keyring = NULL, *key_type = NULL, *key_description = NULL; + const char *sep; /* Stick with cryptsetup --link-vk-to-keyring format * <keyring_description>::%<key_type>:<key_description>, * where %<key_type> is optional and defaults to 'user'. */ - if (!(sep = strstr(val, "::"))) + sep = strstr(val, "::"); + if (!sep) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse link-volume-key= option value: %s", val); /* cryptsetup (cli) supports <keyring_description> passed in various formats: @@ -582,7 +612,8 @@ static int parse_one_option(const char *option) { /* %<key_type> is optional (and defaults to 'user') */ if (*sep == '%') { /* must be separated by colon */ - if (!(c = strchr(sep, ':'))) + const char *c = strchr(sep, ':'); + if (!c) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse link-volume-key= option value: %s", val); key_type = strndup(sep + 1, c - sep - 1); @@ -634,6 +665,17 @@ static int parse_crypt_config(const char *options) { log_warning("skip= ignored with type %s", arg_type); } + if (arg_pkcs11_uri || arg_pkcs11_uri_auto) { + /* If password-cache was not configured explicitly, default to no cache for PKCS#11 */ + if (!arg_password_cache_set) + arg_ask_password_flags &= ~(ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE); + + /* This prevents future backward-compatibility issues if we decide to allow caching for PKCS#11 */ + if (FLAGS_SET(arg_ask_password_flags, ASK_PASSWORD_ACCEPT_CACHED)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Password cache is not supported for PKCS#11 security tokens."); + } + return 0; } @@ -832,13 +874,13 @@ static int get_password( const char *vol, const char *src, usec_t until, - bool accept_cached, + bool ignore_cached, PassphraseType passphrase_type, char ***ret) { _cleanup_free_ char *friendly = NULL, *text = NULL, *disk_path = NULL, *id = NULL; _cleanup_strv_free_erase_ char **passwords = NULL; - AskPasswordFlags flags = arg_ask_password_flags | ASK_PASSWORD_PUSH_CACHE; + AskPasswordFlags flags = arg_ask_password_flags; int r; assert(vol); @@ -871,11 +913,10 @@ static int get_password( .credential = "cryptsetup.passphrase", }; - r = ask_password_auto( - &req, - until, - flags | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED), - &passwords); + if (ignore_cached) + flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + + r = ask_password_auto(&req, until, flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -1080,9 +1121,9 @@ shortcut: static int attach_tcrypt( struct crypt_device *cd, const char *name, + TokenType token_type, const char *key_file, - const void *key_data, - size_t key_data_size, + const struct iovec *key_data, char **passwords, uint32_t flags) { @@ -1098,7 +1139,7 @@ static int attach_tcrypt( assert(name); assert(key_file || key_data || !strv_isempty(passwords)); - if (arg_pkcs11_uri || arg_pkcs11_uri_auto || arg_fido2_device || arg_fido2_device_auto || arg_tpm2_device || arg_tpm2_device_auto) + if (token_type >= 0) /* Ask for a regular password */ return log_error_errno(SYNTHETIC_ERRNO(EAGAIN), "Sorry, but tcrypt devices are currently not supported in conjunction with pkcs11/fido2/tpm2 support."); @@ -1116,8 +1157,8 @@ static int attach_tcrypt( params.veracrypt_pim = arg_tcrypt_veracrypt_pim; if (key_data) { - params.passphrase = key_data; - params.passphrase_size = key_data_size; + params.passphrase = key_data->iov_base; + params.passphrase_size = key_data->iov_len; r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); } else if (key_file) { r = read_one_line_file(key_file, &passphrase); @@ -1161,13 +1202,32 @@ static int attach_tcrypt( return 0; } -static char *make_bindname(const char *volume) { - char *s; +static char *make_bindname(const char *volume, TokenType token_type) { + const char *token_type_name = token_type_to_string(token_type), *suffix; + char *bindname; + int r; + + switch (token_type) { + + case TOKEN_FIDO2: + suffix = "-salt"; + break; - if (asprintf(&s, "@%" PRIx64"/cryptsetup/%s", random_u64(), volume) < 0) + default: + suffix = NULL; + } + + r = asprintf(&bindname, + "@%" PRIx64"/cryptsetup%s%s%s/%s", + random_u64(), + token_type_name ? "-" : "", + strempty(token_type_name), + strempty(suffix), + volume); + if (r < 0) return NULL; - return s; + return bindname; } static int make_security_device_monitor( @@ -1260,6 +1320,10 @@ static bool use_token_plugins(void) { return false; #endif + /* Disable tokens if we're in FIDO2 mode with manual parameters. */ + if (arg_fido2_cid) + return false; + #if HAVE_LIBCRYPTSETUP_PLUGINS int r; @@ -1311,7 +1375,7 @@ static int crypt_activate_by_token_pin_ask_password( const char *credential) { #if HAVE_LIBCRYPTSETUP_PLUGINS - AskPasswordFlags flags = arg_ask_password_flags | ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; + AskPasswordFlags flags = arg_ask_password_flags; _cleanup_strv_free_erase_ char **pins = NULL; int r; @@ -1958,8 +2022,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( static int attach_luks_or_plain_or_bitlk_by_key_data( struct crypt_device *cd, const char *name, - const void *key_data, - size_t key_data_size, + const struct iovec *key_data, uint32_t flags, bool pass_volume_key) { @@ -1970,9 +2033,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_data( assert(key_data); if (pass_volume_key) - r = measured_crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags); + r = measured_crypt_activate_by_volume_key(cd, name, key_data->iov_base, key_data->iov_len, flags); else - r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags); + r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data->iov_base, key_data->iov_len, flags); if (r == -EPERM) { log_error_errno(r, "Failed to activate. (Key incorrect?)"); return -EAGAIN; /* Log actual error, but return EAGAIN */ @@ -2000,7 +2063,7 @@ static int attach_luks_or_plain_or_bitlk_by_key_file( assert(key_file); /* If we read the key via AF_UNIX, make this client recognizable */ - bindname = make_bindname(name); + bindname = make_bindname(name, /* token_type= */ _TOKEN_TYPE_INVALID); if (!bindname) return log_oom(); @@ -2070,9 +2133,9 @@ static int attach_luks_or_plain_or_bitlk_by_passphrase( static int attach_luks_or_plain_or_bitlk( struct crypt_device *cd, const char *name, + TokenType token_type, const char *key_file, - const void *key_data, - size_t key_data_size, + const struct iovec *key_data, char **passwords, uint32_t flags, usec_t until) { @@ -2136,14 +2199,14 @@ static int attach_luks_or_plain_or_bitlk( crypt_get_volume_key_size(cd)*8, crypt_get_device_name(cd)); - if (arg_tpm2_device || arg_tpm2_device_auto) - return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, &IOVEC_MAKE(key_data, key_data_size), until, flags, pass_volume_key); - if (arg_fido2_device || arg_fido2_device_auto) - return attach_luks_or_plain_or_bitlk_by_fido2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key); - if (arg_pkcs11_uri || arg_pkcs11_uri_auto) - return attach_luks_or_plain_or_bitlk_by_pkcs11(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key); + if (token_type == TOKEN_TPM2) + return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, key_data, until, flags, pass_volume_key); + if (token_type == TOKEN_FIDO2) + return attach_luks_or_plain_or_bitlk_by_fido2(cd, name, key_file, key_data->iov_base, key_data->iov_len, until, flags, pass_volume_key); + if (token_type == TOKEN_PKCS11) + return attach_luks_or_plain_or_bitlk_by_pkcs11(cd, name, key_file, key_data->iov_base, key_data->iov_len, until, flags, pass_volume_key); if (key_data) - return attach_luks_or_plain_or_bitlk_by_key_data(cd, name, key_data, key_data_size, flags, pass_volume_key); + return attach_luks_or_plain_or_bitlk_by_key_data(cd, name, key_data, flags, pass_volume_key); if (key_file) return attach_luks_or_plain_or_bitlk_by_key_file(cd, name, key_file, flags, pass_volume_key); @@ -2251,6 +2314,44 @@ static void remove_and_erasep(const char **p) { log_warning_errno(r, "Unable to erase key file '%s', ignoring: %m", *p); } +static TokenType determine_token_type(void) { + if (arg_tpm2_device || arg_tpm2_device_auto) + return TOKEN_TPM2; + if (arg_fido2_device || arg_fido2_device_auto) + return TOKEN_FIDO2; + if (arg_pkcs11_uri || arg_pkcs11_uri_auto) + return TOKEN_PKCS11; + + return _TOKEN_TYPE_INVALID; +} + +static int discover_key(const char *key_file, const char *volume, TokenType token_type, struct iovec *ret_key_data) { + _cleanup_free_ char *bindname = NULL; + const char *token_type_name; + int r; + + assert(key_file); + assert(volume); + assert(ret_key_data); + + bindname = make_bindname(volume, token_type); + if (!bindname) + return log_oom(); + + /* If a key file is not explicitly specified, search for a key in a well defined search path, and load it. */ + r = find_key_file(key_file, STRV_MAKE("/etc/cryptsetup-keys.d", "/run/cryptsetup-keys.d"), bindname, ret_key_data); + if (r <= 0) + return r; + + token_type_name = token_type_to_string(token_type); + if (token_type_name) + log_debug("Automatically discovered encrypted key for volume '%s' (token type: %s).", volume, token_type_name); + else + log_debug("Automatically discovered key for volume '%s'.", volume); + + return r; +} + static int run(int argc, char *argv[]) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; const char *verb; @@ -2273,9 +2374,7 @@ static int run(int argc, char *argv[]) { if (streq(verb, "attach")) { _unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; - _cleanup_(erase_and_freep) void *key_data = NULL; crypt_status_info status; - size_t key_data_size = 0; uint32_t flags = 0; unsigned tries; usec_t until; @@ -2313,28 +2412,7 @@ static int run(int argc, char *argv[]) { /* A delicious drop of snake oil */ (void) mlockall(MCL_FUTURE); - if (!key_file) { - _cleanup_free_ char *bindname = NULL; - const char *fn; - - bindname = make_bindname(volume); - if (!bindname) - return log_oom(); - - /* If a key file is not explicitly specified, search for a key in a well defined - * search path, and load it. */ - - fn = strjoina(volume, ".key"); - r = find_key_file( - fn, - STRV_MAKE("/etc/cryptsetup-keys.d", "/run/cryptsetup-keys.d"), - bindname, - &key_data, &key_data_size); - if (r < 0) - return r; - if (r > 0) - log_debug("Automatically discovered key for volume '%s'.", volume); - } else if (arg_keyfile_erase) + if (key_file && arg_keyfile_erase) destroy_key_file = key_file; /* let's get this baby erased when we leave */ if (arg_header) { @@ -2397,7 +2475,7 @@ static int run(int argc, char *argv[]) { } /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */ - if (!key_file && !key_data && use_token_plugins()) { + if (!key_file && use_token_plugins()) { r = crypt_activate_by_token_pin_ask_password( cd, volume, @@ -2426,33 +2504,40 @@ static int run(int argc, char *argv[]) { } #endif - bool use_cached_passphrase = true; + bool use_cached_passphrase = true, try_discover_key = !key_file; + const char *discovered_key_fn = strjoina(volume, ".key"); _cleanup_strv_free_erase_ char **passwords = NULL; for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { + _cleanup_(iovec_done_erase) struct iovec discovered_key_data = {}; + const struct iovec *key_data = NULL; + TokenType token_type = determine_token_type(); + log_debug("Beginning attempt %u to unlock.", tries); /* When we were able to acquire multiple keys, let's always process them in this order: * * 1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip - * 2. The discovered key: i.e. key_data + key_data_size - * 3. The configured key: i.e. key_file + arg_keyfile_offset + arg_keyfile_size - * 4. The empty password, in case arg_try_empty_password is set - * 5. We enquire the user for a password + * 2. The configured or discovered key, of which both are exclusive and optional + * 3. The empty password, in case arg_try_empty_password is set + * 4. We enquire the user for a password */ - if (!passwords && !key_file && !key_data && !arg_pkcs11_uri && !arg_pkcs11_uri_auto && !arg_fido2_device && !arg_fido2_device_auto && !arg_tpm2_device && !arg_tpm2_device_auto) { + if (try_discover_key) { + r = discover_key(discovered_key_fn, volume, token_type, &discovered_key_data); + if (r < 0) + return r; + if (r > 0) + key_data = &discovered_key_data; + } + + if (token_type < 0 && !key_file && !key_data && !passwords) { /* If we have nothing to try anymore, then acquire a new password */ if (arg_try_empty_password) { /* Hmm, let's try an empty password now, but only once */ arg_try_empty_password = false; - - key_data = strdup(""); - if (!key_data) - return log_oom(); - - key_data_size = 0; + key_data = &iovec_empty; } else { /* Ask the user for a passphrase or recovery key only as last resort, if we have * nothing else to check for */ @@ -2462,7 +2547,13 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered."); } - r = get_password(volume, source, until, use_cached_passphrase && !arg_verify, passphrase_type, &passwords); + r = get_password( + volume, + source, + until, + /* ignore_cached= */ !use_cached_passphrase || arg_verify, + passphrase_type, + &passwords); use_cached_passphrase = false; if (r == -EAGAIN) continue; @@ -2472,9 +2563,9 @@ static int run(int argc, char *argv[]) { } if (streq_ptr(arg_type, CRYPT_TCRYPT)) - r = attach_tcrypt(cd, volume, key_file, key_data, key_data_size, passwords, flags); + r = attach_tcrypt(cd, volume, token_type, key_file, key_data, passwords, flags); else - r = attach_luks_or_plain_or_bitlk(cd, volume, key_file, key_data, key_data_size, passwords, flags, until); + r = attach_luks_or_plain_or_bitlk(cd, volume, token_type, key_file, key_data, passwords, flags, until); if (r >= 0) break; if (r != -EAGAIN) @@ -2483,27 +2574,26 @@ static int run(int argc, char *argv[]) { /* Key not correct? Let's try again, but let's invalidate one of the passed fields, * so that we fallback to the next best thing. */ - if (arg_tpm2_device || arg_tpm2_device_auto) { + if (token_type == TOKEN_TPM2) { arg_tpm2_device = mfree(arg_tpm2_device); arg_tpm2_device_auto = false; continue; } - if (arg_fido2_device || arg_fido2_device_auto) { + if (token_type == TOKEN_FIDO2) { arg_fido2_device = mfree(arg_fido2_device); arg_fido2_device_auto = false; continue; } - if (arg_pkcs11_uri || arg_pkcs11_uri_auto) { + if (token_type == TOKEN_PKCS11) { arg_pkcs11_uri = mfree(arg_pkcs11_uri); arg_pkcs11_uri_auto = false; continue; } - if (key_data) { - key_data = erase_and_free(key_data); - key_data_size = 0; + if (try_discover_key) { + try_discover_key = false; continue; } diff --git a/src/fundamental/iovec-util-fundamental.h b/src/fundamental/iovec-util-fundamental.h new file mode 100644 index 0000000000..68d5bf4ee0 --- /dev/null +++ b/src/fundamental/iovec-util-fundamental.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if SD_BOOT +/* struct iovec is a POSIX userspace construct. Let's introduce it also in EFI mode, it's just so useful */ +struct iovec { + void *iov_base; + size_t iov_len; +}; + +static inline void free(void *p); +#endif + +/* This accepts both const and non-const pointers */ +#define IOVEC_MAKE(base, len) \ + (struct iovec) { \ + .iov_base = (void*) (base), \ + .iov_len = (len), \ + } + +static inline void iovec_done(struct iovec *iovec) { + /* A _cleanup_() helper that frees the iov_base in the iovec */ + assert(iovec); + + iovec->iov_base = mfree(iovec->iov_base); + iovec->iov_len = 0; +} + +static inline bool iovec_is_set(const struct iovec *iovec) { + /* Checks if the iovec points to a non-empty chunk of memory */ + return iovec && iovec->iov_len > 0 && iovec->iov_base; +} + +static inline bool iovec_is_valid(const struct iovec *iovec) { + /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */ + return !iovec || (iovec->iov_base || iovec->iov_len == 0); +} diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index bbfdcdb218..913c8b253c 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -546,3 +546,21 @@ static inline uint64_t ALIGN_OFFSET_U64(uint64_t l, uint64_t ali) { #else #define DECLARE_SBAT(text) #endif + +#define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member) +#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member)) + +#define _FOREACH_ARRAY(i, array, num, m, end) \ + for (typeof(array[0]) *i = (array), *end = ({ \ + typeof(num) m = (num); \ + (i && m > 0) ? i + m : NULL; \ + }); end && i < end; i++) + +#define FOREACH_ARRAY(i, array, num) \ + _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) + +#define FOREACH_ELEMENT(i, array) \ + FOREACH_ARRAY(i, array, ELEMENTSOF(array)) + +#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) +#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index f5f57ac3cb..3e9866ef70 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -5,6 +5,7 @@ fundamental_include = include_directories('.') fundamental_sources = files( 'bootspec-fundamental.c', 'efivars-fundamental.c', + 'iovec-util-fundamental.h', 'sha256-fundamental.c', 'string-util-fundamental.c', 'uki.c', diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index b49419664b..39c6d8a545 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -6,10 +6,13 @@ #include "ask-password-api.h" #include "errno-util.h" +#include "fido2-util.h" #include "format-table.h" #include "hexdecoct.h" #include "homectl-fido2.h" #include "homectl-pkcs11.h" +#include "iovec-util.h" +#include "json-util.h" #include "libcrypt-util.h" #include "libfido2-util.h" #include "locale-util.h" @@ -66,8 +69,7 @@ static int add_fido2_salt( sd_json_variant **v, const void *cid, size_t cid_size, - const void *fido2_salt, - size_t fido2_salt_size, + const struct iovec *salt, const void *secret, size_t secret_size, Fido2EnrollFlags lock_with) { @@ -77,6 +79,11 @@ static int add_fido2_salt( ssize_t base64_encoded_size; int r; + assert(v); + assert(cid); + assert(iovec_is_set(salt)); + assert(secret); + /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends * expect a NUL terminated string, and we use a binary key */ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); @@ -89,7 +96,7 @@ static int add_fido2_salt( r = sd_json_buildo(&e, SD_JSON_BUILD_PAIR("credential", SD_JSON_BUILD_BASE64(cid, cid_size)), - SD_JSON_BUILD_PAIR("salt", SD_JSON_BUILD_BASE64(fido2_salt, fido2_salt_size)), + SD_JSON_BUILD_PAIR("salt", JSON_BUILD_IOVEC_BASE64(salt)), SD_JSON_BUILD_PAIR("hashedPassword", SD_JSON_BUILD_STRING(hashed)), SD_JSON_BUILD_PAIR("up", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UP))), SD_JSON_BUILD_PAIR("uv", SD_JSON_BUILD_BOOLEAN(FLAGS_SET(lock_with, FIDO2ENROLL_UV))), @@ -103,11 +110,11 @@ static int add_fido2_salt( r = sd_json_variant_append_array(&l, e); if (r < 0) - return log_error_errno(r, "Failed append FIDO2 salt: %m"); + return log_error_errno(r, "Failed to append FIDO2 salt: %m"); r = sd_json_variant_set_field(&w, "fido2HmacSalt", l); if (r < 0) - return log_error_errno(r, "Failed to set FDO2 salt: %m"); + return log_error_errno(r, "Failed to set FIDO2 salt: %m"); r = sd_json_variant_set_field(v, "privileged", w); if (r < 0) @@ -125,9 +132,10 @@ int identity_add_fido2_parameters( #if HAVE_LIBFIDO2 sd_json_variant *un, *realm, *rn; - _cleanup_(erase_and_freep) void *secret = NULL, *salt = NULL; + _cleanup_(iovec_done) struct iovec salt = {}; + _cleanup_(erase_and_freep) void *secret = NULL; _cleanup_(erase_and_freep) char *used_pin = NULL; - size_t cid_size, salt_size, secret_size; + size_t cid_size, secret_size; _cleanup_free_ void *cid = NULL; const char *fido_un; int r; @@ -158,6 +166,10 @@ int identity_add_fido2_parameters( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "realName field of user record is not a string"); + r = fido2_generate_salt(&salt); + if (r < 0) + return r; + r = fido2_generate_hmac_hash( device, /* rp_id= */ "io.systemd.home", @@ -170,8 +182,8 @@ int identity_add_fido2_parameters( /* askpw_credential= */ "home.token-pin", lock_with, cred_alg, + &salt, &cid, &cid_size, - &salt, &salt_size, &secret, &secret_size, &used_pin, &lock_with); @@ -189,8 +201,7 @@ int identity_add_fido2_parameters( v, cid, cid_size, - salt, - salt_size, + &salt, secret, secret_size, lock_with); diff --git a/src/home/homectl.c b/src/home/homectl.c index 3c5763d9ab..65431db8ee 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -39,13 +39,13 @@ #include "path-util.h" #include "percent-util.h" #include "pkcs11-util.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "proc-cmdline.h" #include "process-util.h" #include "recurse-dir.h" #include "rlimit-util.h" #include "rm-rf.h" -#include "spawn-polkit-agent.h" #include "terminal-util.h" #include "tmpfile-util.h" #include "uid-classification.h" diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 83e53a4560..157ac769ca 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -23,9 +23,9 @@ #include "hostname-util.h" #include "main-func.h" #include "parse-argument.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "socket-util.h" -#include "spawn-polkit-agent.h" #include "terminal-util.h" #include "verbs.h" diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index cda1205e26..a0bf1bfdc8 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -1621,11 +1621,13 @@ static int vl_method_describe(Varlink *link, sd_json_variant *parameters, Varlin if (r != 0) return r; - r = varlink_verify_polkit_async( + r = varlink_verify_polkit_async_full( link, c->bus, "org.freedesktop.hostname1.get-hardware-serial", /* details= */ NULL, + UID_INVALID, + POLKIT_DONT_REPLY, &c->polkit_registry); if (r == 0) return 0; /* No authorization for now, but the async polkit stuff will call us again when it has it */ @@ -1634,9 +1636,6 @@ static int vl_method_describe(Varlink *link, sd_json_variant *parameters, Varlin * the product ID which we'll check explicitly. */ privileged = r > 0; - if (sd_json_variant_elements(parameters) > 0) - return varlink_error_invalid_parameter(link, parameters); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; r = build_describe_response(c, privileged, &v); if (r < 0) diff --git a/src/import/import-generator.c b/src/import/import-generator.c new file mode 100644 index 0000000000..a7660c44e0 --- /dev/null +++ b/src/import/import-generator.c @@ -0,0 +1,289 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-json.h" + +#include "creds-util.h" +#include "discover-image.h" +#include "fd-util.h" +#include "fileio.h" +#include "generator.h" +#include "import-util.h" +#include "json-util.h" +#include "proc-cmdline.h" +#include "specifier.h" +#include "web-util.h" + +static const char *arg_dest = NULL; +static char *arg_success_action = NULL; +static char *arg_failure_action = NULL; +static sd_json_variant **arg_transfers = NULL; +static size_t arg_n_transfers = 0; + +STATIC_DESTRUCTOR_REGISTER(arg_success_action, freep); +STATIC_DESTRUCTOR_REGISTER(arg_failure_action, freep); +STATIC_ARRAY_DESTRUCTOR_REGISTER(arg_transfers, arg_n_transfers, sd_json_variant_unref_many); + +static int parse_pull_expression(const char *v) { + const char *p = v; + int r; + + assert(v); + + _cleanup_free_ char *options = NULL; + r = extract_first_word(&p, &options, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to extract option string from pull expression '%s': %m", v); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No option string in pull expression '%s': %m", v); + + _cleanup_free_ char *local = NULL; + r = extract_first_word(&p, &local, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to extract local name from pull expression '%s': %m", v); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No local string in pull expression '%s': %m", v); + + if (!http_url_is_valid(p) && !file_url_is_valid(p)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid URL, refusing: %s", p); + _cleanup_free_ char *remote = strdup(p); + if (!remote) + return log_oom(); + + if (isempty(local)) + local = mfree(local); + else if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid image name, refusing: %s", local); + + ImportType type = _IMPORT_TYPE_INVALID; + ImageClass class = _IMAGE_CLASS_INVALID; + ImportVerify verify = IMPORT_VERIFY_SIGNATURE; + bool ro = false; + + const char *o = options; + for (;;) { + _cleanup_free_ char *opt = NULL; + + r = extract_first_word(&o, &opt, ",", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to extract option from pull option expression '%s': %m", options); + if (r == 0) + break; + + const char *suffix; + + if (streq(opt, "ro")) + ro = true; + else if (streq(opt, "rw")) + ro = false; + else if ((suffix = startswith(opt, "verify="))) { + + ImportVerify w = import_verify_from_string(suffix); + + if (w < 0) + log_warning_errno(w, "Unknown verification mode, ignoring: %s", suffix); + else + verify = w; + } else { + ImageClass c; + + c = image_class_from_string(opt); + if (c < 0) { + ImportType t; + + t = import_type_from_string(opt); + if (t < 0) + log_warning_errno(c, "Unknown pull option, ignoring: %s", opt); + else + type = t; + } else + class = c; + } + } + + if (type < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No image type (raw, tar) specified in pull expression, refusing: %s", v); + if (class < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No image class (machine, portable, sysext, confext) specified in pull expression, refusing: %s", v); + + if (!GREEDY_REALLOC(arg_transfers, arg_n_transfers + 1)) + return log_oom(); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; + + r = sd_json_buildo( + &j, + SD_JSON_BUILD_PAIR("remote", SD_JSON_BUILD_STRING(remote)), + SD_JSON_BUILD_PAIR_CONDITION(!!local, "local", SD_JSON_BUILD_STRING(local)), + SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(class))), + SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(import_type_to_string(type))), + SD_JSON_BUILD_PAIR("readOnly", SD_JSON_BUILD_BOOLEAN(ro)), + SD_JSON_BUILD_PAIR("verify", JSON_BUILD_STRING_UNDERSCORIFY(import_verify_to_string(verify)))); + if (r < 0) + return log_error_errno(r, "Failed to build import JSON object: %m"); + + arg_transfers[arg_n_transfers++] = TAKE_PTR(j); + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + if (proc_cmdline_key_streq(key, "systemd.pull")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_pull_expression(value); + if (r < 0) + log_warning_errno(r, "Failed to parse %s expression, ignoring: %s", key, value); + + } else if (proc_cmdline_key_streq(key, "systemd.pull.success_action")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + return free_and_strdup_warn(&arg_success_action, value); + + } else if (proc_cmdline_key_streq(key, "systemd.pull.failure_action")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + return free_and_strdup_warn(&arg_failure_action, value); + } + + return 0; +} + +static int parse_credentials(void) { + _cleanup_free_ char *b = NULL; + size_t sz = 0; + int r; + + r = read_credential_with_decryption("import.pull", (void**) &b, &sz); + if (r <= 0) + return r; + + _cleanup_fclose_ FILE *f = NULL; + f = fmemopen_unlocked(b, sz, "r"); + if (!f) + return log_oom(); + + for (;;) { + _cleanup_free_ char *item = NULL; + + r = read_stripped_line(f, LINE_MAX, &item); + if (r == 0) + break; + if (r < 0) { + log_error_errno(r, "Failed to parse credential 'ssh.listen': %m"); + break; + } + + if (startswith(item, "#")) + continue; + + r = parse_pull_expression(item); + if (r < 0) + log_warning_errno(r, "Failed to parse expression, ignoring: %s", item); + } + + return 0; +} + +static int transfer_generate(sd_json_variant *v, size_t c) { + int r; + + assert(v); + + _cleanup_free_ char *service = NULL; + if (asprintf(&service, "import%zu.service", c) < 0) + return log_oom(); + + _cleanup_fclose_ FILE *f = NULL; + r = generator_open_unit_file(arg_dest, /* source = */ NULL, service, &f); + if (r < 0) + return r; + + const char *remote = sd_json_variant_string(sd_json_variant_by_key(v, "remote")); + + fprintf(f, + "[Unit]\n" + "Description=Download of %s\n" + "Documentation=man:systemd-import-generator(8)\n" + "SourcePath=/proc/cmdline\n" + "Requires=systemd-importd.socket\n" + "After=systemd-importd.socket\n" + "Conflicts=shutdown.target\n" + "Before=shutdown.target\n" + "DefaultDependencies=no\n", + remote); + + if (arg_success_action) + fprintf(f, "SuccessAction=%s\n", + arg_success_action); + + if (arg_failure_action) + fprintf(f, "FailureAction=%s\n", + arg_failure_action); + + const char *class = sd_json_variant_string(sd_json_variant_by_key(v, "class")); + if (streq_ptr(class, "sysext")) + fputs("Before=systemd-sysext.service\n", f); + else if (streq_ptr(class, "confext")) + fputs("Before=systemd-confext.service\n", f); + + /* Assume network resource unless URL is file:// */ + if (!file_url_is_valid(remote)) + fputs("Wants=network-online.target\n" + "After=network-online.target\n", f); + + fputs("\n" + "[Service]\n" + "Type=oneshot\n" + "NotifyAccess=main\n", f); + + _cleanup_free_ char *formatted = NULL; + r = sd_json_variant_format(v, /* flags= */ 0, &formatted); + if (r < 0) + return log_error_errno(r, "Failed to format import JSON data: %m"); + + _cleanup_free_ char *escaped = specifier_escape(formatted); + if (!escaped) + return log_oom(); + + fprintf(f, "ExecStart=:varlinkctl call -q --more /run/systemd/io.systemd.Import io.systemd.Import.Pull '%s'\n", + escaped); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit %s: %m", service); + + return generator_add_symlink(arg_dest, "multi-user.target", "wants", service); +} + +static int generate(void) { + size_t c = 0; + int r = 0; + + FOREACH_ARRAY(i, arg_transfers, arg_n_transfers) + RET_GATHER(r, transfer_generate(*i, c++)); + + return r; +} + +static int run(const char *dest, const char *dest_early, const char *dest_late) { + int r; + + assert_se(arg_dest = dest); + + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_RD_STRICT|PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + (void) parse_credentials(); + + return generate(); +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/import/importctl.c b/src/import/importctl.c index d81e79e3cd..3334f37b94 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -24,10 +24,10 @@ #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "signal-util.h" #include "sort-util.h" -#include "spawn-polkit-agent.h" #include "string-table.h" #include "verbs.h" #include "web-util.h" diff --git a/src/import/importd.c b/src/import/importd.c index c6ca31556a..112180c846 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -22,6 +22,7 @@ #include "hostname-util.h" #include "import-common.h" #include "import-util.h" +#include "json-util.h" #include "machine-pool.h" #include "main-func.h" #include "missing_capability.h" @@ -39,6 +40,8 @@ #include "strv.h" #include "syslog-util.h" #include "user-util.h" +#include "varlink.h" +#include "varlink-io.systemd.Import.h" #include "web-util.h" typedef struct Transfer Transfer; @@ -87,11 +90,14 @@ struct Transfer { int stdin_fd; int stdout_fd; + + Set *varlink_subscribed; }; struct Manager { sd_event *event; sd_bus *bus; + VarlinkServer *varlink_server; uint32_t current_transfer_id; Hashmap *transfers; @@ -120,6 +126,8 @@ static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType); +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(varlink_hash_ops, void, trivial_hash_func, trivial_compare_func, Varlink, varlink_unref); + static Transfer *transfer_unref(Transfer *t) { if (!t) return NULL; @@ -141,6 +149,8 @@ static Transfer *transfer_unref(Transfer *t) { safe_close(t->stdin_fd); safe_close(t->stdout_fd); + set_free(t->varlink_subscribed); + return mfree(t); } @@ -218,7 +228,16 @@ static void transfer_send_log_line(Transfer *t, const char *line) { priority, line); if (r < 0) - log_warning_errno(r, "Cannot emit log message signal, ignoring: %m"); + log_warning_errno(r, "Cannot emit log message bus signal, ignoring: %m"); + + r = varlink_many_notifybo( + t->varlink_subscribed, + SD_JSON_BUILD_PAIR("log", + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_UNSIGNED("priority", priority), + SD_JSON_BUILD_PAIR_STRING("message", line)))); + if (r < 0) + log_warning_errno(r, "Cannot emit log message varlink message, ignoring: %m"); } static void transfer_send_progress_update(Transfer *t) { @@ -229,15 +248,23 @@ static void transfer_send_progress_update(Transfer *t) { if (t->progress_percent_sent == t->progress_percent) return; + double progress = transfer_percent_as_double(t); + r = sd_bus_emit_signal( t->manager->bus, t->object_path, "org.freedesktop.import1.Transfer", "ProgressUpdate", "d", - transfer_percent_as_double(t)); + progress); if (r < 0) - log_warning_errno(r, "Cannot emit progress update signal, ignoring: %m"); + log_warning_errno(r, "Cannot emit progress update bus signal, ignoring: %m"); + + r = varlink_many_notifybo( + t->varlink_subscribed, + SD_JSON_BUILD_PAIR_REAL("progress", progress)); + if (r < 0) + log_warning_errno(r, "Cannot emit progress update varlink message, ignoring: %m"); t->progress_percent_sent = t->progress_percent; } @@ -314,10 +341,18 @@ static int transfer_finalize(Transfer *t, bool success) { t->object_path, success ? "done" : t->n_canceled > 0 ? "canceled" : "failed"); - if (r < 0) log_error_errno(r, "Cannot emit message: %m"); + if (success) + r = varlink_many_reply(t->varlink_subscribed, NULL); + else if (t->n_canceled > 0) + r = varlink_many_error(t->varlink_subscribed, "io.systemd.Import.TransferCancelled", NULL); + else + r = varlink_many_error(t->varlink_subscribed, "io.systemd.Import.TransferFailed", NULL); + if (r < 0) + log_warning_errno(r, "Cannot emit varlink reply, ignoring: %m"); + transfer_unref(t); return 0; } @@ -587,6 +622,8 @@ static Manager *manager_unref(Manager *m) { hashmap_free(m->polkit_registry); m->bus = sd_bus_flush_close_unref(m->bus); + m->varlink_server = varlink_server_unref(m->varlink_server); + sd_event_unref(m->event); return mfree(m); @@ -1706,7 +1743,7 @@ static int manager_connect_bus(Manager *m) { assert(m->event); assert(!m->bus); - r = sd_bus_default_system(&m->bus); + r = bus_open_system_watch_bind(&m->bus); if (r < 0) return log_error_errno(r, "Failed to get system bus connection: %m"); @@ -1729,11 +1766,248 @@ static int manager_connect_bus(Manager *m) { return 0; } +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_image_class, ImageClass, image_class_from_string); + +static int make_transfer_json(Transfer *t, sd_json_variant **ret) { + int r; + + assert(t); + + r = sd_json_buildo(ret, + SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_UNSIGNED(t->id)), + SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(transfer_type_to_string(t->type))), + SD_JSON_BUILD_PAIR("remote", SD_JSON_BUILD_STRING(t->remote)), + SD_JSON_BUILD_PAIR("local", SD_JSON_BUILD_STRING(t->local)), + SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(t->class))), + SD_JSON_BUILD_PAIR("percent", SD_JSON_BUILD_REAL(transfer_percent_as_double(t)))); + if (r < 0) + return log_error_errno(r, "Failed to build transfer JSON data: %m"); + + return 0; +} + +static int vl_method_list_transfers(Varlink *link, sd_json_variant *parameters, VarlinkMethodFlags flags, void *userdata) { + + struct p { + ImageClass class; + } p = { + .class = _IMAGE_CLASS_INVALID, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "class", SD_JSON_VARIANT_STRING, json_dispatch_image_class, offsetof(struct p, class), 0 }, + {}, + }; + + Manager *m = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!FLAGS_SET(flags, VARLINK_METHOD_MORE)) + return varlink_error(link, VARLINK_ERROR_EXPECTED_MORE, NULL); + + Transfer *previous = NULL, *t; + HASHMAP_FOREACH(t, m->transfers) { + + if (p.class >= 0 && p.class != t->class) + continue; + + if (previous) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + r = make_transfer_json(previous, &v); + if (r < 0) + return r; + + r = varlink_notify(link, v); + if (r < 0) + return r; + } + + previous = t; + } + + if (previous) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + r = make_transfer_json(previous, &v); + if (r < 0) + return r; + + return varlink_reply(link, v); + } + + return varlink_error(link, "io.systemd.Import.NoTransfers", NULL); +} + +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_import_verify, ImportVerify, import_verify_from_string); +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_import_type, ImportType, import_type_from_string); + +static int vl_method_pull(Varlink *link, sd_json_variant *parameters, VarlinkMethodFlags flags, void *userdata) { + + struct p { + const char *remote, *local; + ImageClass class; + ImportType type; + ImportVerify verify; + bool force; + bool read_only; + bool keep_download; + } p = { + .class = _IMAGE_CLASS_INVALID, + .verify = IMPORT_VERIFY_SIGNATURE, + }; + + static const sd_json_dispatch_field dispatch_table[] = { + { "remote", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct p, remote), SD_JSON_MANDATORY }, + { "local", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct p, local), 0 }, + { "class", SD_JSON_VARIANT_STRING, json_dispatch_image_class, offsetof(struct p, class), SD_JSON_MANDATORY }, + { "type", SD_JSON_VARIANT_STRING, json_dispatch_import_type, offsetof(struct p, type), SD_JSON_MANDATORY }, + { "verify", SD_JSON_VARIANT_STRING, json_dispatch_import_verify, offsetof(struct p, verify), SD_JSON_STRICT }, + { "force", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, force), 0 }, + { "readOnly", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, read_only), 0 }, + { "keepDownload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct p, keep_download), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {}, + }; + + Manager *m = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!http_url_is_valid(p.remote) && !file_url_is_valid(p.remote)) + return varlink_error_invalid_parameter_name(link, "remote"); + + if (p.local && !image_name_is_valid(p.local)) + return varlink_error_invalid_parameter_name(link, "local"); + + uint64_t transfer_flags = (p.force * IMPORT_FORCE) | (p.read_only * IMPORT_READ_ONLY) | (p.keep_download * IMPORT_PULL_KEEP_DOWNLOAD); + + TransferType tt = + p.type == IMPORT_TAR ? TRANSFER_PULL_TAR : + p.type == IMPORT_RAW ? TRANSFER_PULL_RAW : _TRANSFER_TYPE_INVALID; + + assert(tt >= 0); + + if (manager_find(m, tt, p.remote)) + return varlink_errorbo(link, "io.systemd.Import.AlreadyInProgress", SD_JSON_BUILD_PAIR_STRING("remote", p.remote)); + + r = varlink_verify_polkit_async( + link, + m->bus, + "org.freedesktop.import1.pull", + (const char**) STRV_MAKE( + "remote", p.remote, + "local", p.local, + "class", image_class_to_string(p.class), + "type", import_type_to_string(p.type), + "verify", import_verify_to_string(p.verify)), + &m->polkit_registry); + if (r <= 0) + return r; + + _cleanup_(transfer_unrefp) Transfer *t = NULL; + + r = transfer_new(m, &t); + if (r < 0) + return r; + + t->type = tt; + t->verify = p.verify; + t->flags = transfer_flags; + t->class = p.class; + + t->remote = strdup(p.remote); + if (!t->remote) + return -ENOMEM; + + if (p.local) { + t->local = strdup(p.local); + if (!t->local) + return -ENOMEM; + } + + r = transfer_start(t); + if (r < 0) + return r; + + /* If more was not set, just return the download id, and be done with it */ + if (!FLAGS_SET(flags, VARLINK_METHOD_MORE)) + return varlink_replybo(link, SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_UNSIGNED(t->id))); + + /* Otherwise add this connection to the set of subscriptions, return the id, but keep the thing running */ + r = set_ensure_put(&t->varlink_subscribed, &varlink_hash_ops, link); + if (r < 0) + return r; + + varlink_ref(link); + + r = varlink_notifybo(link, SD_JSON_BUILD_PAIR("id", SD_JSON_BUILD_UNSIGNED(t->id))); + if (r < 0) + return r; + + TAKE_PTR(t); + return 0; +} + +static int manager_connect_varlink(Manager *m) { + int r; + + assert(m); + assert(m->event); + assert(!m->varlink_server); + + r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + varlink_server_set_userdata(m->varlink_server, m); + + r = varlink_server_add_interface(m->varlink_server, &vl_interface_io_systemd_Import); + if (r < 0) + return log_error_errno(r, "Failed to add Import interface to varlink server: %m"); + + r = varlink_server_bind_method_many( + m->varlink_server, + "io.systemd.Import.ListTransfers", vl_method_list_transfers, + "io.systemd.Import.Pull", vl_method_pull); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method calls: %m"); + + r = varlink_server_attach_event(m->varlink_server, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach Varlink server to event loop: %m"); + + r = varlink_server_listen_auto(m->varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m"); + if (r == 0) { + r = varlink_server_listen_address(m->varlink_server, "/run/systemd/io.systemd.Import", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to Varlink socket: %m"); + } + + return 0; +} + static bool manager_check_idle(void *userdata) { Manager *m = ASSERT_PTR(userdata); return hashmap_isempty(m->transfers) && - hashmap_isempty(m->polkit_registry); + hashmap_isempty(m->polkit_registry) && + varlink_server_current_connections(m->varlink_server) == 0; } static void manager_parse_env(Manager *m) { @@ -1786,6 +2060,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = manager_connect_varlink(m); + if (r < 0) + return r; + r = sd_notify(false, NOTIFY_READY); if (r < 0) log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); diff --git a/src/import/meson.build b/src/import/meson.build index 184dd7bbf2..45500edb43 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -110,6 +110,11 @@ executables += [ 'conditions' : ['ENABLE_IMPORTD'], 'sources' : files('importctl.c'), }, + generator_template + { + 'name' : 'systemd-import-generator', + 'sources' : files('import-generator.c'), + 'conditions' : ['ENABLE_IMPORTD'], + }, test_template + { 'sources' : files( 'test-qcow2.c', diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c index 4123fda5f9..95135decaa 100644 --- a/src/journal/journald-kmsg.c +++ b/src/journal/journald-kmsg.c @@ -258,11 +258,9 @@ void dev_kmsg_record(Server *s, char *p, size_t l) { iovec[n++] = IOVEC_MAKE_STRING(source_boot_time); /* Historically, we stored the timestamp 'usec' as _SOURCE_MONOTONIC_TIMESTAMP, so we cannot remove - * the field as it is already used in other projects. So, let's store the correct timestamp here by - * mapping the boottime to monotonic. Then, the existence of _SOURCE_BOOTTIME_TIMESTAMP indicates - * the reliability of _SOURCE_MONOTONIC_TIMESTAMP field. */ + * the field as it is already used in other projects. This is for backward compatibility. */ char source_monotonic_time[STRLEN("_SOURCE_MONOTONIC_TIMESTAMP=") + DECIMAL_STR_MAX(unsigned long long)]; - xsprintf(source_monotonic_time, "_SOURCE_MONOTONIC_TIMESTAMP="USEC_FMT, map_clock_usec(usec, CLOCK_BOOTTIME, CLOCK_MONOTONIC)); + xsprintf(source_monotonic_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", usec); iovec[n++] = IOVEC_MAKE_STRING(source_monotonic_time); iovec[n++] = IOVEC_MAKE_STRING("_TRANSPORT=kernel"); diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index de12ec5e9e..a4b54f6619 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -59,6 +59,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_BUSY, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NOT_YOUR_DEVICE, EPERM), + /* needs to be EOPNOTSUPP for proper handling in callers of logind_schedule_shutdown() */ + SD_BUS_ERROR_MAP(BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY), SD_BUS_ERROR_MAP(BUS_ERROR_NO_NTP_SUPPORT, EOPNOTSUPP), diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 94dc85d301..4ef42af7a9 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -59,6 +59,8 @@ #define BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED "org.freedesktop.login1.SleepVerbNotSupported" #define BUS_ERROR_SESSION_BUSY "org.freedesktop.login1.SessionBusy" #define BUS_ERROR_NOT_YOUR_DEVICE "org.freedesktop.login1.NotYourDevice" +#define BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED \ + "org.freedesktop.login1.DesignatedMaintenanceTimeNotScheduled" #define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled" #define BUS_ERROR_NO_NTP_SUPPORT "org.freedesktop.timedate1.NoNTPSupport" diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c index f415797700..a5146fd6e6 100644 --- a/src/libsystemd/sd-bus/bus-error.c +++ b/src/libsystemd/sd-bus/bus-error.c @@ -62,12 +62,10 @@ extern const sd_bus_error_map __stop_SYSTEMD_BUS_ERROR_MAP[]; static const sd_bus_error_map **additional_error_maps = NULL; static int bus_error_name_to_errno(const char *name) { - const sd_bus_error_map **map, *m; const char *p; int r; - if (!name) - return EINVAL; + assert_return(name, EINVAL); p = startswith(name, "System.Error."); if (p) { @@ -79,8 +77,8 @@ static int bus_error_name_to_errno(const char *name) { } if (additional_error_maps) - for (map = additional_error_maps; *map; map++) - for (m = *map;; m++) { + for (const sd_bus_error_map **map = additional_error_maps; *map; map++) + for (const sd_bus_error_map *m = *map;; m++) { /* For additional error maps the end marker is actually the end marker */ if (m->code == BUS_ERROR_MAP_END_MARKER) break; @@ -91,25 +89,22 @@ static int bus_error_name_to_errno(const char *name) { } } - m = ALIGN_PTR(__start_SYSTEMD_BUS_ERROR_MAP); - while (m < __stop_SYSTEMD_BUS_ERROR_MAP) { - /* For magic ELF error maps, the end marker might - * appear in the middle of things, since multiple maps - * might appear in the same section. Hence, let's skip - * over it, but realign the pointer to the next 8 byte - * boundary, which is the selected alignment for the - * arrays. */ - if (m->code == BUS_ERROR_MAP_END_MARKER) { - m = ALIGN_PTR(m + 1); + const sd_bus_error_map *elf_map = ALIGN_PTR(__start_SYSTEMD_BUS_ERROR_MAP); + while (elf_map < __stop_SYSTEMD_BUS_ERROR_MAP) { + /* For magic ELF error maps, the end marker might appear in the middle of things, since + * multiple maps might appear in the same section. Hence, let's skip over it, but realign + * the pointer to the next 8 byte boundary, which is the selected alignment for the arrays. */ + if (elf_map->code == BUS_ERROR_MAP_END_MARKER) { + elf_map = ALIGN_PTR(elf_map + 1); continue; } - if (streq(m->name, name)) { - assert(m->code > 0); - return m->code; + if (streq(elf_map->name, name)) { + assert(elf_map->code > 0); + return elf_map->code; } - m++; + elf_map++; } return EIO; @@ -389,7 +384,7 @@ _public_ int sd_bus_error_has_names_sentinel(const sd_bus_error *e, ...) { return !!p; } -_public_ int sd_bus_error_get_errno(const sd_bus_error* e) { +_public_ int sd_bus_error_get_errno(const sd_bus_error *e) { if (!e || !e->name) return 0; diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index 0b48158a4c..fdfd5522db 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -60,8 +60,16 @@ struct json_variant_foreach_state { return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(n)); \ \ type cc = func(sd_json_variant_string(variant)); \ - if (cc < 0) \ - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized.", strna(n)); \ + if (cc < 0) { \ + /* Maybe this enum is recognizable if we replace "_" (i.e. Varlink syntax) with "-" (how we usually prefer it). */ \ + _cleanup_free_ char *z = strreplace(sd_json_variant_string(variant), "_", "-"); \ + if (!z) \ + return json_log_oom(variant, flags); \ + \ + cc = func(z); \ + if (cc < 0) \ + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized: %s", strna(n), sd_json_variant_string(variant)); \ + } \ \ *c = cc; \ return 0; \ @@ -130,6 +138,8 @@ enum { _JSON_BUILD_IOVEC_HEX, _JSON_BUILD_HW_ADDR, _JSON_BUILD_STRING_SET, + _JSON_BUILD_STRING_UNDERSCORIFY, + _JSON_BUILD_DUAL_TIMESTAMP, _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, _JSON_BUILD_PAIR_FINITE_USEC, @@ -156,6 +166,8 @@ enum { #define JSON_BUILD_ETHER_ADDR(v) SD_JSON_BUILD_BYTE_ARRAY(((const struct ether_addr*) { v })->ether_addr_octet, sizeof(struct ether_addr)) #define JSON_BUILD_HW_ADDR(v) _JSON_BUILD_HW_ADDR, (const struct hw_addr_data*) { v } #define JSON_BUILD_STRING_SET(s) _JSON_BUILD_STRING_SET, (Set *) { s } +#define JSON_BUILD_STRING_UNDERSCORIFY(s) _JSON_BUILD_STRING_UNDERSCORIFY, (const char *) { s } +#define JSON_BUILD_DUAL_TIMESTAMP(t) _JSON_BUILD_DUAL_TIMESTAMP, (dual_timestamp*) { t } #define JSON_BUILD_PAIR_UNSIGNED_NON_ZERO(name, u) _JSON_BUILD_PAIR_UNSIGNED_NON_ZERO, (const char*) { name }, (uint64_t) { u } #define JSON_BUILD_PAIR_FINITE_USEC(name, u) _JSON_BUILD_PAIR_FINITE_USEC, (const char*) { name }, (usec_t) { u } diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 5f9c593205..1bccb2091b 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -3500,7 +3500,8 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { switch (command) { - case _SD_JSON_BUILD_STRING: { + case _SD_JSON_BUILD_STRING: + case _JSON_BUILD_STRING_UNDERSCORIFY: { const char *p; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { @@ -3511,6 +3512,18 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { p = va_arg(ap, const char *); if (current->n_suppress == 0) { + _cleanup_free_ char *c = NULL; + + if (command == _JSON_BUILD_STRING_UNDERSCORIFY) { + c = strreplace(p, "-", "_"); + if (!c) { + r = -ENOMEM; + goto finish; + } + + p = c; + } + r = sd_json_variant_new_string(&add, p); if (r < 0) goto finish; @@ -4066,6 +4079,40 @@ _public_ int sd_json_buildv(sd_json_variant **ret, va_list ap) { break; } + case _JSON_BUILD_DUAL_TIMESTAMP: { + dual_timestamp *ts; + + if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { + r = -EINVAL; + goto finish; + } + + ts = va_arg(ap, dual_timestamp*); + + if (current->n_suppress == 0) { + if (dual_timestamp_is_set(ts)) { + r = sd_json_buildo( + &add, + SD_JSON_BUILD_PAIR("realtime", SD_JSON_BUILD_UNSIGNED(ts->realtime)), + SD_JSON_BUILD_PAIR("monotonic", SD_JSON_BUILD_UNSIGNED(ts->monotonic))); + if (r < 0) + return r; + } else + add = JSON_VARIANT_MAGIC_NULL; + } + + n_subtract = 1; + + if (current->expect == EXPECT_TOPLEVEL) + current->expect = EXPECT_END; + else if (current->expect == EXPECT_OBJECT_VALUE) + current->expect = EXPECT_OBJECT_KEY; + else + assert(current->expect == EXPECT_ARRAY_ELEMENT); + + break; + } + case _SD_JSON_BUILD_CALLBACK: { sd_json_build_callback_t cb; void *userdata; diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 9a2163bcfe..5b00820e5a 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -17,10 +17,10 @@ #include "main-func.h" #include "memory-util.h" #include "pager.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "proc-cmdline.h" #include "set.h" -#include "spawn-polkit-agent.h" #include "strv.h" #include "terminal-util.h" #include "verbs.h" diff --git a/src/login/inhibit.c b/src/login/inhibit.c index 4682830d19..13ba4b82f4 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -25,10 +25,11 @@ #include "terminal-util.h" #include "user-util.h" -static const char* arg_what = "idle:sleep:shutdown"; -static const char* arg_who = NULL; -static const char* arg_why = "Unknown reason"; -static const char* arg_mode = NULL; +static const char *arg_what = "idle:sleep:shutdown"; +static const char *arg_who = NULL; +static const char *arg_why = "Unknown reason"; +static const char *arg_mode = NULL; +static bool arg_ask_password = true; static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; @@ -42,6 +43,8 @@ static int inhibit(sd_bus *bus, sd_bus_error *error) { int r; int fd; + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + r = bus_call_method(bus, bus_login_mgr, "Inhibit", error, &reply, "ssss", arg_what, arg_who, arg_why, arg_mode); if (r < 0) return r; @@ -145,6 +148,7 @@ static int help(void) { "\n%sExecute a process while inhibiting shutdown/sleep/idle.%s\n\n" " -h --help Show this help\n" " --version Show package version\n" + " --no-ask-password Do not attempt interactive authorization\n" " --no-pager Do not pipe output into a pager\n" " --no-legend Do not show the headers and footers\n" " --what=WHAT Operations to inhibit, colon separated list of:\n" @@ -173,20 +177,22 @@ static int parse_argv(int argc, char *argv[]) { ARG_WHY, ARG_MODE, ARG_LIST, + ARG_NO_ASK_PASSWORD, ARG_NO_PAGER, ARG_NO_LEGEND, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "what", required_argument, NULL, ARG_WHAT }, - { "who", required_argument, NULL, ARG_WHO }, - { "why", required_argument, NULL, ARG_WHY }, - { "mode", required_argument, NULL, ARG_MODE }, - { "list", no_argument, NULL, ARG_LIST }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "what", required_argument, NULL, ARG_WHAT }, + { "who", required_argument, NULL, ARG_WHO }, + { "why", required_argument, NULL, ARG_WHY }, + { "mode", required_argument, NULL, ARG_MODE }, + { "list", no_argument, NULL, ARG_LIST }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, {} }; @@ -228,6 +234,10 @@ static int parse_argv(int argc, char *argv[]) { arg_action = ACTION_LIST; break; + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; @@ -267,6 +277,8 @@ static int run(int argc, char *argv[]) { if (r < 0) return bus_log_connect_error(r, BUS_TRANSPORT_LOCAL); + (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); + if (arg_action == ACTION_LIST) return print_inhibitors(bus); else { diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 36421da0ad..7ca07c4efe 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -25,12 +25,12 @@ #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "process-util.h" #include "rlimit-util.h" #include "sigbus.h" #include "signal-util.h" -#include "spawn-polkit-agent.h" #include "string-table.h" #include "strv.h" #include "sysfs-show.h" diff --git a/src/login/logind-action.c b/src/login/logind-action.c index 9325d91549..686ba358f7 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -12,6 +12,7 @@ #include "format-util.h" #include "logind-action.h" #include "logind-dbus.h" +#include "logind-seat-dbus.h" #include "logind-session-dbus.h" #include "process-util.h" #include "special.h" @@ -299,12 +300,55 @@ static int handle_action_sleep_execute( return handle_action_execute(m, handle, ignore_inhibited, is_edge); } +static int manager_handle_action_secure_attention_key( + Manager *m, + bool is_edge, + const char *seat) { + + int r; + Seat *o; + _cleanup_free_ char *p = NULL; + + assert(m); + + if (!is_edge) + return 0; + + if (!seat) + return 0; + + o = hashmap_get(m->seats, seat); + if (!o) + return 0; + + p = seat_bus_path(o); + if (!p) + return log_oom(); + + log_struct(LOG_INFO, + LOG_MESSAGE("Secure Attention Key sequence pressed on seat %s", seat), + "MESSAGE_ID=" SD_MESSAGE_SECURE_ATTENTION_KEY_PRESS_STR, + "SEAT_ID=%s", seat); + + r = sd_bus_emit_signal( + m->bus, + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SecureAttentionKey", + "so", seat, p); + if (r < 0) + log_warning_errno(r, "Failed to emit SecureAttentionKey signal, ignoring: %m"); + + return 0; +} + int manager_handle_action( Manager *m, InhibitWhat inhibit_key, HandleAction handle, bool ignore_inhibited, - bool is_edge) { + bool is_edge, + const char *action_seat) { assert(m); assert(handle_action_valid(handle)); @@ -336,7 +380,7 @@ int manager_handle_action( } } - /* Locking is handled differently from the rest. */ + /* Locking and greeter activation is handled differently from the rest. */ if (handle == HANDLE_LOCK) { if (!is_edge) return 0; @@ -346,6 +390,9 @@ int manager_handle_action( return 1; } + if (handle == HANDLE_SECURE_ATTENTION_KEY) + return manager_handle_action_secure_attention_key(m, is_edge, action_seat); + if (HANDLE_ACTION_IS_SLEEP(handle)) return handle_action_sleep_execute(m, handle, ignore_inhibited, is_edge); @@ -366,6 +413,7 @@ static const char* const handle_action_verb_table[_HANDLE_ACTION_MAX] = { [HANDLE_SLEEP] = "sleep", [HANDLE_FACTORY_RESET] = "perform a factory reset", [HANDLE_LOCK] = "be locked", + [HANDLE_SECURE_ATTENTION_KEY] = "handle the secure attention key", }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(handle_action_verb, HandleAction); @@ -386,6 +434,7 @@ static const char* const handle_action_table[_HANDLE_ACTION_MAX] = { [HANDLE_SLEEP] = "sleep", [HANDLE_FACTORY_RESET] = "factory-reset", [HANDLE_LOCK] = "lock", + [HANDLE_SECURE_ATTENTION_KEY] = "secure-attention-key", }; DEFINE_STRING_TABLE_LOOKUP(handle_action, HandleAction); diff --git a/src/login/logind-action.h b/src/login/logind-action.h index c78c18c5aa..800f4e8309 100644 --- a/src/login/logind-action.h +++ b/src/login/logind-action.h @@ -22,6 +22,7 @@ typedef enum HandleAction { HANDLE_SLEEP, /* A "high-level" action that automatically choose an appropriate low-level sleep action */ _HANDLE_ACTION_SLEEP_LAST = HANDLE_SLEEP, + HANDLE_SECURE_ATTENTION_KEY, HANDLE_LOCK, HANDLE_FACTORY_RESET, @@ -77,7 +78,8 @@ int manager_handle_action( InhibitWhat inhibit_key, HandleAction handle, bool ignore_inhibited, - bool is_edge); + bool is_edge, + const char *action_seat); const char* handle_action_verb_to_string(HandleAction h) _const_; diff --git a/src/login/logind-button.c b/src/login/logind-button.c index 14835aedc1..f980ea5ece 100644 --- a/src/login/logind-button.c +++ b/src/login/logind-button.c @@ -15,7 +15,36 @@ #include "missing_input.h" #include "string-util.h" -#define CONST_MAX5(a, b, c, d, e) CONST_MAX(CONST_MAX(a, b), CONST_MAX(CONST_MAX(c, d), e)) +/* KEY_RESTART is the highest value key in keys_interested. */ +#define _KEY_MAX_INTERESTED KEY_RESTART + +/* Adding more values here may require _KEY_MAX_INTERESTED to be updated, this array must be sorted by key value. */ +static const int keys_interested[] = { + KEY_ESC, + KEY_LEFTCTRL, + KEY_LEFTSHIFT, + KEY_RIGHTSHIFT, + KEY_LEFTALT, + KEY_RIGHTCTRL, + KEY_RIGHTALT, + KEY_POWER, + KEY_SLEEP, + KEY_SUSPEND, + KEY_POWER2, + KEY_RESTART, +}; + +static const struct { + int keycode; + ButtonModifierMask modifier_mask; +} keycode_modifier_table[] = { + { KEY_LEFTSHIFT, BUTTON_MODIFIER_LEFT_SHIFT }, + { KEY_RIGHTSHIFT, BUTTON_MODIFIER_RIGHT_SHIFT }, + { KEY_LEFTCTRL, BUTTON_MODIFIER_LEFT_CTRL }, + { KEY_RIGHTCTRL, BUTTON_MODIFIER_RIGHT_CTRL }, + { KEY_LEFTALT, BUTTON_MODIFIER_LEFT_ALT }, + { KEY_RIGHTALT, BUTTON_MODIFIER_RIGHT_ALT }, +}; #define ULONG_BITS (sizeof(unsigned long)*8) @@ -48,6 +77,8 @@ Button* button_new(Manager *m, const char *name) { return mfree(b); } + b->button_modifier_mask = BUTTON_MODIFIER_NONE; + b->manager = m; b->fd = -EBADF; @@ -77,7 +108,18 @@ int button_set_seat(Button *b, const char *sn) { return free_and_strdup(&b->seat, sn); } -static void button_lid_switch_handle_action(Manager *manager, bool is_edge) { +static ButtonModifierMask button_get_keycode_modifier_mask(int keycode) { + + assert(keycode > 0); + + FOREACH_ELEMENT(i, keycode_modifier_table) + if (i->keycode == keycode) + return i->modifier_mask; + + return BUTTON_MODIFIER_NONE; +} + +static void button_lid_switch_handle_action(Manager *manager, bool is_edge, const char* seat) { HandleAction handle_action; assert(manager); @@ -91,7 +133,7 @@ static void button_lid_switch_handle_action(Manager *manager, bool is_edge) { else handle_action = manager->handle_lid_switch; - manager_handle_action(manager, INHIBIT_HANDLE_LID_SWITCH, handle_action, manager->lid_switch_ignore_inhibited, is_edge); + manager_handle_action(manager, INHIBIT_HANDLE_LID_SWITCH, handle_action, manager->lid_switch_ignore_inhibited, is_edge, seat); } static int button_recheck(sd_event_source *e, void *userdata) { @@ -99,7 +141,7 @@ static int button_recheck(sd_event_source *e, void *userdata) { assert(b->lid_closed); - button_lid_switch_handle_action(b->manager, false); + button_lid_switch_handle_action(b->manager, /* is_edge= */ false, b->seat); return 1; } @@ -120,7 +162,8 @@ static int button_install_check_event_source(Button *b) { } static int long_press_of_power_key_handler(sd_event_source *e, uint64_t usec, void *userdata) { - Manager *m = ASSERT_PTR(userdata); + Button *b = ASSERT_PTR(userdata); + Manager *m = ASSERT_PTR(b->manager); assert(e); @@ -130,12 +173,13 @@ static int long_press_of_power_key_handler(sd_event_source *e, uint64_t usec, vo LOG_MESSAGE("Power key pressed long."), "MESSAGE_ID=" SD_MESSAGE_POWER_KEY_LONG_PRESS_STR); - manager_handle_action(m, INHIBIT_HANDLE_POWER_KEY, m->handle_power_key_long_press, m->power_key_ignore_inhibited, true); + manager_handle_action(m, INHIBIT_HANDLE_POWER_KEY, m->handle_power_key_long_press, m->power_key_ignore_inhibited, /* is_edge= */ true, b->seat); return 0; } static int long_press_of_reboot_key_handler(sd_event_source *e, uint64_t usec, void *userdata) { - Manager *m = ASSERT_PTR(userdata); + Button *b = ASSERT_PTR(userdata); + Manager *m = ASSERT_PTR(b->manager); assert(e); @@ -145,12 +189,13 @@ static int long_press_of_reboot_key_handler(sd_event_source *e, uint64_t usec, v LOG_MESSAGE("Reboot key pressed long."), "MESSAGE_ID=" SD_MESSAGE_REBOOT_KEY_LONG_PRESS_STR); - manager_handle_action(m, INHIBIT_HANDLE_REBOOT_KEY, m->handle_reboot_key_long_press, m->reboot_key_ignore_inhibited, true); + manager_handle_action(m, INHIBIT_HANDLE_REBOOT_KEY, m->handle_reboot_key_long_press, m->reboot_key_ignore_inhibited, /* is_edge= */ true, b->seat); return 0; } static int long_press_of_suspend_key_handler(sd_event_source *e, uint64_t usec, void *userdata) { - Manager *m = ASSERT_PTR(userdata); + Button *b = ASSERT_PTR(userdata); + Manager *m = ASSERT_PTR(b->manager); assert(e); @@ -160,12 +205,13 @@ static int long_press_of_suspend_key_handler(sd_event_source *e, uint64_t usec, LOG_MESSAGE("Suspend key pressed long."), "MESSAGE_ID=" SD_MESSAGE_SUSPEND_KEY_LONG_PRESS_STR); - manager_handle_action(m, INHIBIT_HANDLE_SUSPEND_KEY, m->handle_suspend_key_long_press, m->suspend_key_ignore_inhibited, true); + manager_handle_action(m, INHIBIT_HANDLE_SUSPEND_KEY, m->handle_suspend_key_long_press, m->suspend_key_ignore_inhibited, /* is_edge= */ true, b->seat); return 0; } static int long_press_of_hibernate_key_handler(sd_event_source *e, uint64_t usec, void *userdata) { - Manager *m = ASSERT_PTR(userdata); + Button *b = ASSERT_PTR(userdata); + Manager *m = ASSERT_PTR(b->manager); assert(e); @@ -175,15 +221,17 @@ static int long_press_of_hibernate_key_handler(sd_event_source *e, uint64_t usec LOG_MESSAGE("Hibernate key pressed long."), "MESSAGE_ID=" SD_MESSAGE_HIBERNATE_KEY_LONG_PRESS_STR); - manager_handle_action(m, INHIBIT_HANDLE_HIBERNATE_KEY, m->handle_hibernate_key_long_press, m->hibernate_key_ignore_inhibited, true); + manager_handle_action(m, INHIBIT_HANDLE_HIBERNATE_KEY, m->handle_hibernate_key_long_press, m->hibernate_key_ignore_inhibited, /* is_edge= */ true, b->seat); return 0; } -static void start_long_press(Manager *m, sd_event_source **e, sd_event_time_handler_t callback) { +static void start_long_press(Button *b, sd_event_source **e, sd_event_time_handler_t callback) { + Manager *m; int r; - assert(m); + assert(b); assert(e); + m = ASSERT_PTR(b->manager); if (*e) return; @@ -193,7 +241,7 @@ static void start_long_press(Manager *m, sd_event_source **e, sd_event_time_hand e, CLOCK_MONOTONIC, LONG_PRESS_DURATION, 0, - callback, m); + callback, b); if (r < 0) log_warning_errno(r, "Failed to add long press timer event, ignoring: %m"); } @@ -220,12 +268,12 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u case KEY_POWER2: if (b->manager->handle_power_key_long_press != HANDLE_IGNORE && b->manager->handle_power_key_long_press != b->manager->handle_power_key) { log_debug("Power key pressed. Further action depends on the key press duration."); - start_long_press(b->manager, &b->manager->power_key_long_press_event_source, long_press_of_power_key_handler); + start_long_press(b, &b->manager->power_key_long_press_event_source, long_press_of_power_key_handler); } else { log_struct(LOG_INFO, LOG_MESSAGE("Power key pressed short."), "MESSAGE_ID=" SD_MESSAGE_POWER_KEY_STR); - manager_handle_action(b->manager, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, true); + manager_handle_action(b->manager, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, /* is_edge= */ true, b->seat); } break; @@ -237,12 +285,12 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u case KEY_RESTART: if (b->manager->handle_reboot_key_long_press != HANDLE_IGNORE && b->manager->handle_reboot_key_long_press != b->manager->handle_reboot_key) { log_debug("Reboot key pressed. Further action depends on the key press duration."); - start_long_press(b->manager, &b->manager->reboot_key_long_press_event_source, long_press_of_reboot_key_handler); + start_long_press(b, &b->manager->reboot_key_long_press_event_source, long_press_of_reboot_key_handler); } else { log_struct(LOG_INFO, LOG_MESSAGE("Reboot key pressed short."), "MESSAGE_ID=" SD_MESSAGE_REBOOT_KEY_STR); - manager_handle_action(b->manager, INHIBIT_HANDLE_REBOOT_KEY, b->manager->handle_reboot_key, b->manager->reboot_key_ignore_inhibited, true); + manager_handle_action(b->manager, INHIBIT_HANDLE_REBOOT_KEY, b->manager->handle_reboot_key, b->manager->reboot_key_ignore_inhibited, /* is_edge= */ true, b->seat); } break; @@ -255,26 +303,42 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u case KEY_SLEEP: if (b->manager->handle_suspend_key_long_press != HANDLE_IGNORE && b->manager->handle_suspend_key_long_press != b->manager->handle_suspend_key) { log_debug("Suspend key pressed. Further action depends on the key press duration."); - start_long_press(b->manager, &b->manager->suspend_key_long_press_event_source, long_press_of_suspend_key_handler); + start_long_press(b, &b->manager->suspend_key_long_press_event_source, long_press_of_suspend_key_handler); } else { log_struct(LOG_INFO, LOG_MESSAGE("Suspend key pressed short."), "MESSAGE_ID=" SD_MESSAGE_SUSPEND_KEY_STR); - manager_handle_action(b->manager, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, true); + manager_handle_action(b->manager, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, /* is_edge= */ true, b->seat); } break; case KEY_SUSPEND: if (b->manager->handle_hibernate_key_long_press != HANDLE_IGNORE && b->manager->handle_hibernate_key_long_press != b->manager->handle_hibernate_key) { log_debug("Hibernate key pressed. Further action depends on the key press duration."); - start_long_press(b->manager, &b->manager->hibernate_key_long_press_event_source, long_press_of_hibernate_key_handler); + start_long_press(b, &b->manager->hibernate_key_long_press_event_source, long_press_of_hibernate_key_handler); } else { log_struct(LOG_INFO, LOG_MESSAGE("Hibernate key pressed short."), "MESSAGE_ID=" SD_MESSAGE_HIBERNATE_KEY_STR); - manager_handle_action(b->manager, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, true); + manager_handle_action(b->manager, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, /* is_edge= */ true, b->seat); } break; + + case KEY_ESC: + if (b->manager->handle_secure_attention_key == HANDLE_IGNORE) + break; + if (!BUTTON_MODIFIER_HAS_SHIFT(b->button_modifier_mask) || + !BUTTON_MODIFIER_HAS_CTRL(b->button_modifier_mask) || + !BUTTON_MODIFIER_HAS_ALT(b->button_modifier_mask)) + break; + + log_debug("Secure Attention Key sequence pressed."); + manager_handle_action(b->manager, /* inhibit_key= */ 0, b->manager->handle_secure_attention_key, /* ignore_inhibited= */ true, /* is_edge= */ true, b->seat); + break; + + default: + b->button_modifier_mask |= button_get_keycode_modifier_mask(ev.code); + break; } } else if (ev.type == EV_KEY && ev.value == 0) { @@ -294,7 +358,7 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u b->manager->power_key_long_press_event_source = sd_event_source_unref(b->manager->power_key_long_press_event_source); - manager_handle_action(b->manager, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, true); + manager_handle_action(b->manager, INHIBIT_HANDLE_POWER_KEY, b->manager->handle_power_key, b->manager->power_key_ignore_inhibited, /* is_edge= */ true, b->seat); } break; @@ -306,7 +370,7 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u b->manager->reboot_key_long_press_event_source = sd_event_source_unref(b->manager->reboot_key_long_press_event_source); - manager_handle_action(b->manager, INHIBIT_HANDLE_REBOOT_KEY, b->manager->handle_reboot_key, b->manager->reboot_key_ignore_inhibited, true); + manager_handle_action(b->manager, INHIBIT_HANDLE_REBOOT_KEY, b->manager->handle_reboot_key, b->manager->reboot_key_ignore_inhibited, /* is_edge= */ true, b->seat); } break; @@ -318,7 +382,7 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u b->manager->suspend_key_long_press_event_source = sd_event_source_unref(b->manager->suspend_key_long_press_event_source); - manager_handle_action(b->manager, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, true); + manager_handle_action(b->manager, INHIBIT_HANDLE_SUSPEND_KEY, b->manager->handle_suspend_key, b->manager->suspend_key_ignore_inhibited, /* is_edge= */ true, b->seat); } break; case KEY_SUSPEND: @@ -329,9 +393,16 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u b->manager->hibernate_key_long_press_event_source = sd_event_source_unref(b->manager->hibernate_key_long_press_event_source); - manager_handle_action(b->manager, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, true); + manager_handle_action(b->manager, INHIBIT_HANDLE_HIBERNATE_KEY, b->manager->handle_hibernate_key, b->manager->hibernate_key_ignore_inhibited, /* is_edge= */ true, b->seat); } break; + + case KEY_ESC: + break; + + default: + b->button_modifier_mask &= ~button_get_keycode_modifier_mask(ev.code); + break; } } else if (ev.type == EV_SW && ev.value > 0) { @@ -342,7 +413,7 @@ static int button_dispatch(sd_event_source *s, int fd, uint32_t revents, void *u "MESSAGE_ID=" SD_MESSAGE_LID_CLOSED_STR); b->lid_closed = true; - button_lid_switch_handle_action(b->manager, true); + button_lid_switch_handle_action(b->manager, /* is_edge= */ true, b->seat); button_install_check_event_source(b); manager_send_changed(b->manager, "LidClosed", NULL); @@ -386,17 +457,25 @@ static int button_suitable(int fd) { return -errno; if (bitset_get(types, EV_KEY)) { - unsigned long keys[CONST_MAX5(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND, KEY_RESTART)/ULONG_BITS+1]; + unsigned long keys[_KEY_MAX_INTERESTED/ULONG_BITS+1]; if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof keys), keys) < 0) return -errno; + /* If the device has power related keys, then accept the device. */ if (bitset_get(keys, KEY_POWER) || bitset_get(keys, KEY_POWER2) || bitset_get(keys, KEY_SLEEP) || bitset_get(keys, KEY_SUSPEND) || bitset_get(keys, KEY_RESTART)) return true; + + /* If the device has keys for the Secure Attention Key sequence, then accept the device. */ + if ((bitset_get(keys, KEY_LEFTSHIFT) || bitset_get(keys, KEY_RIGHTSHIFT)) && + (bitset_get(keys, KEY_LEFTCTRL) || bitset_get(keys, KEY_RIGHTCTRL)) && + (bitset_get(keys, KEY_LEFTALT) || bitset_get(keys, KEY_RIGHTALT)) && + bitset_get(keys, KEY_ESC)) + return true; } if (bitset_get(types, EV_SW)) { @@ -413,10 +492,27 @@ static int button_suitable(int fd) { return false; } +static int button_initialize_modifier_mask(Button *b) { + unsigned long keys[KEY_MAX/ULONG_BITS+1]; + + assert(b); + + if (ioctl(b->fd, EVIOCGKEY(sizeof(keys)), keys) < 0) + return log_error_errno(errno, "Failed to initialize modifier mask on /dev/input/%s: %m", b->name); + + b->button_modifier_mask = BUTTON_MODIFIER_NONE; + + FOREACH_ELEMENT(i, keycode_modifier_table) + if (bitset_get(keys, i->keycode)) + b->button_modifier_mask |= i->modifier_mask; + + return 0; +} + static int button_set_mask(const char *name, int fd) { unsigned long types[CONST_MAX(EV_KEY, EV_SW)/ULONG_BITS+1] = {}, - keys[CONST_MAX5(KEY_POWER, KEY_POWER2, KEY_SLEEP, KEY_SUSPEND, KEY_RESTART)/ULONG_BITS+1] = {}, + keys[_KEY_MAX_INTERESTED/ULONG_BITS+1] = {}, switches[CONST_MAX(SW_LID, SW_DOCK)/ULONG_BITS+1] = {}; struct input_mask mask; @@ -437,11 +533,11 @@ static int button_set_mask(const char *name, int fd) { return log_full_errno(IN_SET(errno, ENOTTY, EOPNOTSUPP, EINVAL) ? LOG_DEBUG : LOG_WARNING, errno, "Failed to set EV_SYN event mask on /dev/input/%s: %m", name); - bitset_put(keys, KEY_POWER); - bitset_put(keys, KEY_POWER2); - bitset_put(keys, KEY_SLEEP); - bitset_put(keys, KEY_SUSPEND); - bitset_put(keys, KEY_RESTART); + FOREACH_ELEMENT(key, keys_interested) { + assert(*key <= _KEY_MAX_INTERESTED); + + bitset_put(keys, *key); + } mask = (struct input_mask) { .type = EV_KEY, @@ -502,6 +598,11 @@ int button_open(Button *b) { b->fd = TAKE_FD(fd); log_info("Watching system buttons on %s (%s)", p, name); + + r = button_initialize_modifier_mask(b); + if (r < 0) + return r; + return 0; } diff --git a/src/login/logind-button.h b/src/login/logind-button.h index 6c39471fb4..125e49dd08 100644 --- a/src/login/logind-button.h +++ b/src/login/logind-button.h @@ -5,6 +5,20 @@ typedef struct Button Button; #include "logind.h" +typedef enum ButtonModifierMask { + BUTTON_MODIFIER_NONE = 0, + BUTTON_MODIFIER_LEFT_SHIFT = 1 << 0, + BUTTON_MODIFIER_RIGHT_SHIFT = 1 << 1, + BUTTON_MODIFIER_LEFT_CTRL = 1 << 2, + BUTTON_MODIFIER_RIGHT_CTRL = 1 << 3, + BUTTON_MODIFIER_LEFT_ALT = 1 << 4, + BUTTON_MODIFIER_RIGHT_ALT = 1 << 5, +} ButtonModifierMask; + +#define BUTTON_MODIFIER_HAS_SHIFT(modifier) (((modifier) & (BUTTON_MODIFIER_LEFT_SHIFT|BUTTON_MODIFIER_RIGHT_SHIFT)) != 0) +#define BUTTON_MODIFIER_HAS_CTRL(modifier) (((modifier) & (BUTTON_MODIFIER_LEFT_CTRL|BUTTON_MODIFIER_RIGHT_CTRL)) != 0) +#define BUTTON_MODIFIER_HAS_ALT(modifier) (((modifier) & (BUTTON_MODIFIER_LEFT_ALT|BUTTON_MODIFIER_RIGHT_ALT)) != 0) + struct Button { Manager *manager; @@ -15,6 +29,8 @@ struct Button { char *seat; int fd; + ButtonModifierMask button_modifier_mask; + bool lid_closed; bool docked; }; diff --git a/src/login/logind-core.c b/src/login/logind-core.c index 71e4247a79..5e024c339d 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -50,6 +50,7 @@ void manager_reset_config(Manager *m) { m->handle_suspend_key_long_press = HANDLE_HIBERNATE; m->handle_hibernate_key = HANDLE_HIBERNATE; m->handle_hibernate_key_long_press = HANDLE_IGNORE; + m->handle_secure_attention_key = HANDLE_SECURE_ATTENTION_KEY; m->handle_lid_switch = HANDLE_SUSPEND; m->handle_lid_switch_ep = _HANDLE_ACTION_INVALID; @@ -77,6 +78,8 @@ void manager_reset_config(Manager *m) { m->kill_exclude_users = strv_free(m->kill_exclude_users); m->stop_idle_session_usec = USEC_INFINITY; + + m->maintenance_time = calendar_spec_free(m->maintenance_time); } int manager_parse_config_file(Manager *m) { @@ -694,6 +697,8 @@ bool manager_all_buttons_ignored(Manager *m) { return false; if (m->handle_lid_switch_docked != HANDLE_IGNORE) return false; + if (m->handle_secure_attention_key != HANDLE_IGNORE) + return false; return true; } diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 277561300c..3e419bb62e 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -392,6 +392,31 @@ static int property_get_scheduled_shutdown( return sd_bus_message_close_container(reply); } +static int property_get_maintenance_time( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = ASSERT_PTR(userdata); + _cleanup_free_ char *s = NULL; + int r; + + assert(bus); + assert(reply); + + if (m->maintenance_time) { + r = calendar_spec_to_string(m->maintenance_time, &s); + if (r < 0) + return log_error_errno(r, "Failed to format calendar specification: %m"); + } + + return sd_bus_message_append(reply, "s", s); +} + static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_handle_action, handle_action, HandleAction); static BUS_DEFINE_PROPERTY_GET(property_get_docked, "b", Manager, manager_is_docked_or_external_displays); static BUS_DEFINE_PROPERTY_GET(property_get_lid_closed, "b", Manager, manager_is_lid_closed); @@ -2331,6 +2356,8 @@ static void reset_scheduled_shutdown(Manager *m) { } (void) unlink(SHUTDOWN_SCHEDULE_FILE); + + manager_send_changed(m, "ScheduledShutdown", NULL); } static int update_schedule_file(Manager *m) { @@ -2565,6 +2592,25 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ if (r != 0) return r; + if (elapse == USEC_INFINITY) { + if (m->maintenance_time) { + r = calendar_spec_next_usec(m->maintenance_time, now(CLOCK_REALTIME), &elapse); + if (r < 0) { + if (r == -ENOENT) + return sd_bus_error_set(error, + BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, + "No upcoming maintenance window scheduled"); + return sd_bus_error_setf(error, + BUS_ERROR_DESIGNATED_MAINTENANCE_TIME_NOT_SCHEDULED, + "Failed to determine next maintenace window"); + } + + log_info("Scheduled %s at maintenance window %s", type, FORMAT_TIMESTAMP(elapse)); + } else + /* the good old shutdown command uses one minute by default */ + elapse = usec_add(now(CLOCK_REALTIME), USEC_PER_MINUTE); + } + m->scheduled_shutdown_action = handle; m->shutdown_dry_run = dry_run; m->scheduled_shutdown_timeout = elapse; @@ -2582,6 +2628,8 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ return r; } + manager_send_changed(m, "ScheduledShutdown", NULL); + return sd_bus_reply_method_return(message, NULL); } @@ -3651,12 +3699,14 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PROPERTY("HandleLidSwitch", "s", property_get_handle_action, offsetof(Manager, handle_lid_switch), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("HandleLidSwitchExternalPower", "s", property_get_handle_action, offsetof(Manager, handle_lid_switch_ep), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("HandleLidSwitchDocked", "s", property_get_handle_action, offsetof(Manager, handle_lid_switch_docked), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("HandleSecureAttentionKey", "s", property_get_handle_action, offsetof(Manager, handle_secure_attention_key), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("HoldoffTimeoutUSec", "t", NULL, offsetof(Manager, holdoff_timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IdleAction", "s", property_get_handle_action, offsetof(Manager, idle_action), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IdleActionUSec", "t", NULL, offsetof(Manager, idle_action_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PreparingForShutdown", "b", property_get_preparing, 0, 0), SD_BUS_PROPERTY("PreparingForSleep", "b", property_get_preparing, 0, 0), - SD_BUS_PROPERTY("ScheduledShutdown", "(st)", property_get_scheduled_shutdown, 0, 0), + SD_BUS_PROPERTY("ScheduledShutdown", "(st)", property_get_scheduled_shutdown, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("DesignatedMaintenanceTime", "s", property_get_maintenance_time, 0, 0), SD_BUS_PROPERTY("Docked", "b", property_get_docked, 0, 0), SD_BUS_PROPERTY("LidClosed", "b", property_get_lid_closed, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("OnExternalPower", "b", property_get_on_external_power, 0, 0), @@ -4022,6 +4072,9 @@ static const sd_bus_vtable manager_vtable[] = { method_set_wall_message, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL_WITH_ARGS("SecureAttentionKey", + SD_BUS_ARGS("s", seat_id, "o", object_path), + 0), SD_BUS_SIGNAL_WITH_ARGS("SessionNew", SD_BUS_ARGS("s", session_id, "o", object_path), 0), diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf index 7d1520b469..ce5af2ccfa 100644 --- a/src/login/logind-gperf.gperf +++ b/src/login/logind-gperf.gperf @@ -37,6 +37,7 @@ Login.HandleHibernateKeyLongPress, config_parse_handle_action, 0, offse Login.HandleLidSwitch, config_parse_handle_action, 0, offsetof(Manager, handle_lid_switch) Login.HandleLidSwitchExternalPower, config_parse_handle_action, 0, offsetof(Manager, handle_lid_switch_ep) Login.HandleLidSwitchDocked, config_parse_handle_action, 0, offsetof(Manager, handle_lid_switch_docked) +Login.HandleSecureAttentionKey, config_parse_handle_action, 0, offsetof(Manager, handle_secure_attention_key) Login.PowerKeyIgnoreInhibited, config_parse_bool, 0, offsetof(Manager, power_key_ignore_inhibited) Login.SuspendKeyIgnoreInhibited, config_parse_bool, 0, offsetof(Manager, suspend_key_ignore_inhibited) Login.HibernateKeyIgnoreInhibited, config_parse_bool, 0, offsetof(Manager, hibernate_key_ignore_inhibited) @@ -50,5 +51,6 @@ Login.RuntimeDirectoryInodesMax, config_parse_iec_uint64, 0, offse Login.RemoveIPC, config_parse_bool, 0, offsetof(Manager, remove_ipc) Login.InhibitorsMax, config_parse_uint64, 0, offsetof(Manager, inhibitors_max) Login.SessionsMax, config_parse_uint64, 0, offsetof(Manager, sessions_max) +Login.DesignatedMaintenanceTime, config_parse_calendar, 0, offsetof(Manager, maintenance_time) Login.UserTasksMax, config_parse_compat_user_tasks_max, 0, 0 Login.StopIdleSessionSec, config_parse_sec_fix_0, 0, offsetof(Manager, stop_idle_session_usec) diff --git a/src/login/logind.c b/src/login/logind.c index ac4b8602c4..a0f7e08a2a 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -1036,7 +1036,7 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us else log_info("System idle. Will %s now.", handle_action_verb_to_string(m->idle_action)); - manager_handle_action(m, 0, m->idle_action, false, is_edge); + manager_handle_action(m, /* inhibit_key= */ 0, m->idle_action, /* ignore_inhibited= */ false, is_edge, /* action_seat= */ NULL); m->idle_action_not_before_usec = n; } diff --git a/src/login/logind.conf.in b/src/login/logind.conf.in index b62458ec3c..2e06b9a050 100644 --- a/src/login/logind.conf.in +++ b/src/login/logind.conf.in @@ -36,6 +36,7 @@ #HandleLidSwitch=suspend #HandleLidSwitchExternalPower=suspend #HandleLidSwitchDocked=ignore +#HandleSecureAttentionKey=secure-attention-key #PowerKeyIgnoreInhibited=no #SuspendKeyIgnoreInhibited=no #HibernateKeyIgnoreInhibited=no @@ -50,3 +51,4 @@ #InhibitorsMax=8192 #SessionsMax=8192 #StopIdleSessionSec=infinity +#DesignatedMaintenanceTime= diff --git a/src/login/logind.h b/src/login/logind.h index cac6a30357..1e17b610bc 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -8,6 +8,7 @@ #include "sd-device.h" #include "sd-event.h" +#include "calendarspec.h" #include "conf-parser.h" #include "hashmap.h" #include "list.h" @@ -108,6 +109,7 @@ struct Manager { HandleAction handle_suspend_key_long_press; HandleAction handle_hibernate_key; HandleAction handle_hibernate_key_long_press; + HandleAction handle_secure_attention_key; HandleAction handle_lid_switch; HandleAction handle_lid_switch_ep; @@ -141,6 +143,8 @@ struct Manager { char *efi_loader_entry_one_shot; struct stat efi_loader_entry_one_shot_stat; + + CalendarSpec *maintenance_time; }; void manager_reset_config(Manager *m); diff --git a/src/login/org.freedesktop.login1.policy b/src/login/org.freedesktop.login1.policy index 012ee14483..226bb4cda4 100644 --- a/src/login/org.freedesktop.login1.policy +++ b/src/login/org.freedesktop.login1.policy @@ -22,7 +22,7 @@ <description gettext-domain="systemd">Allow applications to inhibit system shutdown</description> <message gettext-domain="systemd">Authentication is required for an application to inhibit system shutdown.</message> <defaults> - <allow_any>no</allow_any> + <allow_any>auth_admin_keep</allow_any> <allow_inactive>yes</allow_inactive> <allow_active>yes</allow_active> </defaults> @@ -44,7 +44,7 @@ <description gettext-domain="systemd">Allow applications to inhibit system sleep</description> <message gettext-domain="systemd">Authentication is required for an application to inhibit system sleep.</message> <defaults> - <allow_any>no</allow_any> + <allow_any>auth_admin_keep</allow_any> <allow_inactive>yes</allow_inactive> <allow_active>yes</allow_active> </defaults> diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 6f437c06c4..5b3538d416 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -5,6 +5,7 @@ #include "sd-id128.h" #include "sd-json.h" +#include "bus-polkit.h" #include "hostname-util.h" #include "json-util.h" #include "machine-varlink.h" @@ -126,16 +127,18 @@ int vl_method_register(Varlink *link, sd_json_variant *parameters, VarlinkMethod int r; static const sd_json_dispatch_field dispatch_table[] = { - { "name", SD_JSON_VARIANT_STRING, machine_name, offsetof(Machine, name), SD_JSON_MANDATORY }, - { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(Machine, id), 0 }, - { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, service), 0 }, - { "class", SD_JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), SD_JSON_MANDATORY }, - { "leader", SD_JSON_VARIANT_UNSIGNED, machine_leader, offsetof(Machine, leader), 0 }, - { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, root_directory), 0 }, - { "ifIndices", SD_JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, - { "vSockCid", SD_JSON_VARIANT_UNSIGNED, machine_cid, offsetof(Machine, vsock_cid), 0 }, - { "sshAddress", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, ssh_address), SD_JSON_STRICT }, - { "sshPrivateKeyPath", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, ssh_private_key_path), 0 }, + { "name", SD_JSON_VARIANT_STRING, machine_name, offsetof(Machine, name), SD_JSON_MANDATORY }, + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(Machine, id), 0 }, + { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, service), 0 }, + { "class", SD_JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), SD_JSON_MANDATORY }, + { "leader", SD_JSON_VARIANT_UNSIGNED, machine_leader, offsetof(Machine, leader), 0 }, + { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, root_directory), 0 }, + { "ifIndices", SD_JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, + { "vSockCid", SD_JSON_VARIANT_UNSIGNED, machine_cid, offsetof(Machine, vsock_cid), 0 }, + { "sshAddress", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, ssh_address), SD_JSON_STRICT }, + { "sshPrivateKeyPath", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, ssh_private_key_path), 0 }, + { "allocateUnit", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Machine, allocate_unit), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, {} }; @@ -147,6 +150,16 @@ int vl_method_register(Varlink *link, sd_json_variant *parameters, VarlinkMethod if (r != 0) return r; + r = varlink_verify_polkit_async( + link, + manager->bus, + "org.freedesktop.machine1.create-machine", + (const char**) STRV_MAKE("name", machine->name, + "class", machine_class_to_string(machine->class)), + &manager->polkit_registry); + if (r <= 0) + return r; + if (!pidref_is_set(&machine->leader)) { r = varlink_get_peer_pidref(link, &machine->leader); if (r < 0) @@ -159,11 +172,13 @@ int vl_method_register(Varlink *link, sd_json_variant *parameters, VarlinkMethod if (r < 0) return r; - r = cg_pidref_get_unit(&machine->leader, &machine->unit); - if (r < 0) - return r; + if (!machine->allocate_unit) { + r = cg_pidref_get_unit(&machine->leader, &machine->unit); + if (r < 0) + return r; + } - r = machine_start(machine, NULL, NULL); + r = machine_start(machine, /* properties= */ NULL, /* error= */ NULL); if (r < 0) return r; diff --git a/src/machine/machine.c b/src/machine/machine.c index 1ef2be27ab..a12ea7c16b 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -118,6 +118,7 @@ Machine* machine_free(Machine *m) { m->manager->host_machine = NULL; } + m->leader_pidfd_event_source = sd_event_source_disable_unref(m->leader_pidfd_event_source); if (pidref_is_set(&m->leader)) { if (m->manager) (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader.pid), m); @@ -475,6 +476,38 @@ static int machine_ensure_scope(Machine *m, sd_bus_message *properties, sd_bus_e return 0; } +static int machine_dispatch_leader_pidfd(sd_event_source *s, int fd, unsigned revents, void *userdata) { + Machine *m = ASSERT_PTR(userdata); + + m->leader_pidfd_event_source = sd_event_source_disable_unref(m->leader_pidfd_event_source); + machine_add_to_gc_queue(m); + + return 0; +} + +static int machine_watch_pidfd(Machine *m) { + int r; + + assert(m); + assert(m->manager); + assert(pidref_is_set(&m->leader)); + assert(!m->leader_pidfd_event_source); + + if (m->leader.fd < 0) + return 0; + + /* If we have a pidfd for the leader, let's also track it for POLLIN, and GC the machine + * automatically if it dies */ + + r = sd_event_add_io(m->manager->event, &m->leader_pidfd_event_source, m->leader.fd, EPOLLIN, machine_dispatch_leader_pidfd, m); + if (r < 0) + return r; + + (void) sd_event_source_set_description(m->leader_pidfd_event_source, "machine-pidfd"); + + return 0; +} + int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { int r; @@ -490,6 +523,10 @@ int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { if (r < 0) return r; + r = machine_watch_pidfd(m); + if (r < 0) + return r; + /* Create cgroup */ r = machine_ensure_scope(m, properties, error); if (r < 0) @@ -592,6 +629,8 @@ void machine_add_to_gc_queue(Machine *m) { LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m); m->in_gc_queue = true; + + manager_enqueue_gc(m->manager); } MachineState machine_get_state(Machine *s) { diff --git a/src/machine/machine.h b/src/machine/machine.h index 12551025c7..157ac0bb6d 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -49,6 +49,7 @@ struct Machine { char *scope_job; PidRef leader; + sd_event_source *leader_pidfd_event_source; dual_timestamp timestamp; @@ -56,6 +57,7 @@ struct Machine { bool started:1; bool stopping:1; bool referenced:1; + bool allocate_unit; sd_bus_message *create_message; diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 5572115a54..d79282f03f 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -14,6 +14,7 @@ #include "sd-bus.h" #include "alloc-util.h" +#include "ask-password-agent.h" #include "build.h" #include "build-path.h" #include "bus-common-errors.h" @@ -47,6 +48,7 @@ #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "process-util.h" #include "ptyfwd.h" @@ -54,8 +56,6 @@ #include "sigbus.h" #include "signal-util.h" #include "sort-util.h" -#include "spawn-ask-password-agent.h" -#include "spawn-polkit-agent.h" #include "stdio-util.h" #include "string-table.h" #include "strv.h" diff --git a/src/machine/machined-core.c b/src/machine/machined-core.c index ffca209494..a7e1853b53 100644 --- a/src/machine/machined-core.c +++ b/src/machine/machined-core.c @@ -104,3 +104,55 @@ int manager_find_machine_for_gid(Manager *m, gid_t gid, Machine **ret_machine, g return false; } + +void manager_gc(Manager *m, bool drop_not_started) { + Machine *machine; + + assert(m); + + while ((machine = LIST_POP(gc_queue, m->machine_gc_queue))) { + machine->in_gc_queue = false; + + /* First, if we are not closing yet, initiate stopping */ + if (machine_may_gc(machine, drop_not_started) && + machine_get_state(machine) != MACHINE_CLOSING) + machine_stop(machine); + + /* Now, the stop probably made this referenced + * again, but if it didn't, then it's time to let it + * go entirely. */ + if (machine_may_gc(machine, drop_not_started)) { + machine_finalize(machine); + machine_free(machine); + } + } +} + +static int on_deferred_gc(sd_event_source *s, void *userdata) { + manager_gc(userdata, /* drop_not_started= */ true); + return 0; +} + +void manager_enqueue_gc(Manager *m) { + int r; + + assert(m); + + if (m->deferred_gc_event_source) { + r = sd_event_source_set_enabled(m->deferred_gc_event_source, SD_EVENT_ONESHOT); + if (r < 0) + log_warning_errno(r, "Failed to enable GC event source, ignoring: %m"); + + return; + } + + r = sd_event_add_defer(m->event, &m->deferred_gc_event_source, on_deferred_gc, m); + if (r < 0) + return (void) log_warning_errno(r, "Failed to allocate GC event source, ignoring: %m"); + + r = sd_event_source_set_priority(m->deferred_gc_event_source, SD_EVENT_PRIORITY_IDLE); + if (r < 0) + log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m"); + + (void) sd_event_source_set_description(m->deferred_gc_event_source, "deferred-gc"); +} diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index dc35877c49..7c74c0b1a2 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "format-util.h" +#include "hostname-util.h" #include "json-util.h" #include "machine-varlink.h" #include "machined-varlink.h" #include "mkdir.h" +#include "socket-util.h" #include "user-util.h" #include "varlink.h" #include "varlink-io.systemd.Machine.h" @@ -383,6 +385,83 @@ static int vl_method_get_memberships(Varlink *link, sd_json_variant *parameters, return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); } +static int list_machine_one(Varlink *link, Machine *m, bool more) { + int r; + + assert(link); + assert(m); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(m->name)), + SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->id), "id", SD_JSON_BUILD_ID128(m->id)), + SD_JSON_BUILD_PAIR("class", SD_JSON_BUILD_STRING(machine_class_to_string(m->class))), + SD_JSON_BUILD_PAIR_CONDITION(!!m->service, "service", SD_JSON_BUILD_STRING(m->service)), + SD_JSON_BUILD_PAIR_CONDITION(!!m->root_directory, "rootDirectory", SD_JSON_BUILD_STRING(m->root_directory)), + SD_JSON_BUILD_PAIR_CONDITION(!!m->unit, "unit", SD_JSON_BUILD_STRING(m->unit)), + SD_JSON_BUILD_PAIR_CONDITION(pidref_is_set(&m->leader), "leader", SD_JSON_BUILD_UNSIGNED(m->leader.pid)), + SD_JSON_BUILD_PAIR_CONDITION(dual_timestamp_is_set(&m->timestamp), "timestamp", JSON_BUILD_DUAL_TIMESTAMP(&m->timestamp)), + SD_JSON_BUILD_PAIR_CONDITION(m->vsock_cid != VMADDR_CID_ANY, "vSockCid", SD_JSON_BUILD_UNSIGNED(m->vsock_cid)), + SD_JSON_BUILD_PAIR_CONDITION(!!m->ssh_address, "sshAddress", SD_JSON_BUILD_STRING(m->ssh_address))); + if (r < 0) + return r; + + if (more) + return varlink_notify(link, v); + + return varlink_reply(link, v); +} + +static int vl_method_list(Varlink *link, sd_json_variant *parameters, VarlinkMethodFlags flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + const char *mn = NULL; + + const sd_json_dispatch_field dispatch_table[] = { + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, PTR_TO_SIZE(&mn), 0 }, + {} + }; + + int r; + + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, 0); + if (r != 0) + return r; + + if (mn) { + if (!hostname_is_valid(mn, /* flags= */ VALID_HOSTNAME_DOT_HOST)) + return varlink_error_invalid_parameter_name(link, "name"); + + Machine *machine = hashmap_get(m->machines, mn); + if (!machine) + return varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); + + return list_machine_one(link, machine, /* more= */ false); + } + + if (!FLAGS_SET(flags, VARLINK_METHOD_MORE)) + return varlink_error(link, VARLINK_ERROR_EXPECTED_MORE, NULL); + + Machine *previous = NULL, *i; + HASHMAP_FOREACH(i, m->machines) { + if (previous) { + r = list_machine_one(link, previous, /* more= */ true); + if (r < 0) + return r; + } + + previous = i; + } + + if (previous) + return list_machine_one(link, previous, /* more= */ false); + + return varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); +} + static int manager_varlink_init_userdb(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -433,7 +512,7 @@ static int manager_varlink_init_machine(Manager *m) { if (m->varlink_machine_server) return 0; - r = varlink_server_new(&s, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA); + r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m"); @@ -443,7 +522,10 @@ static int manager_varlink_init_machine(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m"); - r = varlink_server_bind_method(s, "io.systemd.Machine.Register", vl_method_register); + r = varlink_server_bind_method_many( + s, + "io.systemd.Machine.Register", vl_method_register, + "io.systemd.Machine.List", vl_method_list); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/machine/machined.c b/src/machine/machined.c index 60badbf5d0..4f2ca626e2 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -97,6 +97,8 @@ static Manager* manager_unref(Manager *m) { sd_event_source_unref(m->nscd_cache_flush_event); #endif + sd_event_source_disable_unref(m->deferred_gc_event_source); + hashmap_free(m->polkit_registry); manager_varlink_done(m); @@ -264,29 +266,6 @@ static int manager_connect_bus(Manager *m) { return 0; } -static void manager_gc(Manager *m, bool drop_not_started) { - Machine *machine; - - assert(m); - - while ((machine = LIST_POP(gc_queue, m->machine_gc_queue))) { - machine->in_gc_queue = false; - - /* First, if we are not closing yet, initiate stopping */ - if (machine_may_gc(machine, drop_not_started) && - machine_get_state(machine) != MACHINE_CLOSING) - machine_stop(machine); - - /* Now, the stop probably made this referenced - * again, but if it didn't, then it's time to let it - * go entirely. */ - if (machine_may_gc(machine, drop_not_started)) { - machine_finalize(machine); - machine_free(machine); - } - } -} - static int manager_startup(Manager *m) { Machine *machine; int r; @@ -331,8 +310,6 @@ static bool check_idle(void *userdata) { if (!hashmap_isempty(m->polkit_registry)) return false; - manager_gc(m, true); - return hashmap_isempty(m->machines); } diff --git a/src/machine/machined.h b/src/machine/machined.h index 67abed0fd6..4c22382f2a 100644 --- a/src/machine/machined.h +++ b/src/machine/machined.h @@ -24,6 +24,8 @@ struct Manager { Hashmap *machine_units; Hashmap *machine_leaders; + sd_event_source *deferred_gc_event_source; + Hashmap *polkit_registry; Hashmap *image_cache; @@ -68,3 +70,6 @@ static inline void manager_enqueue_nscd_cache_flush(Manager *m) {} int manager_find_machine_for_uid(Manager *m, uid_t host_uid, Machine **ret_machine, uid_t *ret_internal_uid); int manager_find_machine_for_gid(Manager *m, gid_t host_gid, Machine **ret_machine, gid_t *ret_internal_gid); + +void manager_gc(Manager *m, bool drop_not_started); +void manager_enqueue_gc(Manager *m); diff --git a/src/machine/org.freedesktop.machine1.policy b/src/machine/org.freedesktop.machine1.policy index f031e4e480..fe125ed0db 100644 --- a/src/machine/org.freedesktop.machine1.policy +++ b/src/machine/org.freedesktop.machine1.policy @@ -91,6 +91,17 @@ <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.shell org.freedesktop.login1.login</annotate> </action> + <action id="org.freedesktop.machine1.create-machine"> + <description gettext-domain="systemd">Create a local virtual machine or container</description> + <message gettext-domain="systemd">Authentication is required to create a local virtual machine or container.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.imply">org.freedesktop.login1.shell org.freedesktop.login1.login</annotate> + </action> + <action id="org.freedesktop.machine1.manage-images"> <description gettext-domain="systemd">Manage local virtual machine and container images</description> <message gettext-domain="systemd">Authentication is required to manage local virtual machine and container images.</message> diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index fcebdcaf18..8ac08e95b8 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -28,10 +28,10 @@ #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "process-util.h" #include "sort-util.h" -#include "spawn-polkit-agent.h" #include "stat-util.h" #include "strv.h" #include "terminal-util.h" diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 4dd6044b18..b08d454d34 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1137,14 +1137,12 @@ static int dhcp_server_is_filtered(Link *link, sd_dhcp_client *client) { return log_link_debug_errno(link, r, "Failed to get DHCP server IP address: %m"); if (in4_address_is_filtered(&addr, link->network->dhcp_allow_listed_ip, link->network->dhcp_deny_listed_ip)) { - if (DEBUG_LOGGING) { - if (link->network->dhcp_allow_listed_ip) - log_link_debug(link, "DHCPv4 server IP address "IPV4_ADDRESS_FMT_STR" not found in allow-list, ignoring offer.", - IPV4_ADDRESS_FMT_VAL(addr)); - else - log_link_debug(link, "DHCPv4 server IP address "IPV4_ADDRESS_FMT_STR" found in deny-list, ignoring offer.", - IPV4_ADDRESS_FMT_VAL(addr)); - } + if (link->network->dhcp_allow_listed_ip) + log_link_debug(link, "DHCPv4 server IP address "IPV4_ADDRESS_FMT_STR" not found in allow-list, ignoring offer.", + IPV4_ADDRESS_FMT_VAL(addr)); + else + log_link_debug(link, "DHCPv4 server IP address "IPV4_ADDRESS_FMT_STR" found in deny-list, ignoring offer.", + IPV4_ADDRESS_FMT_VAL(addr)); return true; } diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 7cafe1f6a3..521a64253c 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -222,20 +222,33 @@ static int ndisc_request_route(Route *route, Link *link) { /* Note, here do not call route_remove_and_cancel() with 'route' directly, otherwise * existing route(s) may be removed needlessly. */ - if (route_get(link->manager, route, &existing) >= 0) { - /* Found an existing route that may conflict with this route. */ + /* First, check if a conflicting route is already requested. If there is an existing route, + * and also an existing pending request, then the source may be updated by the request. So, + * we first need to check the source of the requested route. */ + if (route_get_request(link->manager, route, &req) >= 0) { + existing = ASSERT_PTR(req->userdata); if (!route_can_update(existing, route)) { - log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, removing."); + if (existing->source == NETWORK_CONFIG_SOURCE_STATIC) { + log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, ignoring request."); + return 0; + } + + log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, cancelling."); r = route_remove_and_cancel(existing, link->manager); if (r < 0) return r; } } - if (route_get_request(link->manager, route, &req) >= 0) { - existing = ASSERT_PTR(req->userdata); + /* Then, check if a conflicting route exists. */ + if (route_get(link->manager, route, &existing) >= 0) { if (!route_can_update(existing, route)) { - log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, cancelling."); + if (existing->source == NETWORK_CONFIG_SOURCE_STATIC) { + log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, ignoring request."); + return 0; + } + + log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, removing."); r = route_remove_and_cancel(existing, link->manager); if (r < 0) return r; @@ -291,18 +304,44 @@ static int ndisc_remove_route(Route *route, Link *link) { if (r < 0) return r; - if (route->pref_set) { - ndisc_set_route_priority(link, route); - return route_remove_and_cancel(route, link->manager); - } - - uint8_t pref; + uint8_t pref, pref_original = route->pref; FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) { + Route *existing; + Request *req; + + /* If the preference is specified by the user config (that is, for semi-static routes), + * rather than RA, then only search conflicting routes that have the same preference. */ + if (route->pref_set && pref != pref_original) + continue; + route->pref = pref; ndisc_set_route_priority(link, route); - r = route_remove_and_cancel(route, link->manager); - if (r < 0) - return r; + + /* Unfortunately, we cannot directly pass 'route' to route_remove_and_cancel() here, as the + * same or similar route may be configured or requested statically. */ + + /* First, check if the route is already requested. If there is an existing route, and also an + * existing pending request, then the source may be updated by the request. So, we first need + * to check the source of the requested route. */ + if (route_get_request(link->manager, route, &req) >= 0) { + existing = ASSERT_PTR(req->userdata); + if (existing->source == NETWORK_CONFIG_SOURCE_STATIC) + continue; + + r = route_remove_and_cancel(existing, link->manager); + if (r < 0) + return r; + } + + /* Then, check if the route exists. */ + if (route_get(link->manager, route, &existing) >= 0) { + if (existing->source == NETWORK_CONFIG_SOURCE_STATIC) + continue; + + r = route_remove_and_cancel(existing, link->manager); + if (r < 0) + return r; + } } return 0; @@ -1275,10 +1314,11 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { return log_link_warning_errno(link, r, "Failed to get prefix length: %m"); if (in6_prefix_is_filtered(&a, prefixlen, link->network->ndisc_allow_listed_prefix, link->network->ndisc_deny_listed_prefix)) { - if (DEBUG_LOGGING) - log_link_debug(link, "Prefix '%s' is %s, ignoring", - !set_isempty(link->network->ndisc_allow_listed_prefix) ? "not in allow list" - : "in deny list", + if (set_isempty(link->network->ndisc_allow_listed_prefix)) + log_link_debug(link, "Prefix '%s' is in deny list, ignoring.", + IN6_ADDR_PREFIX_TO_STRING(&a, prefixlen)); + else + log_link_debug(link, "Prefix '%s' is not in allow list, ignoring.", IN6_ADDR_PREFIX_TO_STRING(&a, prefixlen)); return 0; } @@ -1329,11 +1369,11 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { if (in6_prefix_is_filtered(&dst, prefixlen, link->network->ndisc_allow_listed_route_prefix, link->network->ndisc_deny_listed_route_prefix)) { - - if (DEBUG_LOGGING) - log_link_debug(link, "Route prefix %s is %s, ignoring", - !set_isempty(link->network->ndisc_allow_listed_route_prefix) ? "not in allow list" - : "in deny list", + if (set_isempty(link->network->ndisc_allow_listed_route_prefix)) + log_link_debug(link, "Route prefix '%s' is in deny list, ignoring.", + IN6_ADDR_PREFIX_TO_STRING(&dst, prefixlen)); + else + log_link_debug(link, "Route prefix '%s' is not in allow list, ignoring.", IN6_ADDR_PREFIX_TO_STRING(&dst, prefixlen)); return 0; } @@ -2103,12 +2143,10 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); if (in6_prefix_is_filtered(&router, 128, link->network->ndisc_allow_listed_router, link->network->ndisc_deny_listed_router)) { - if (DEBUG_LOGGING) { - if (!set_isempty(link->network->ndisc_allow_listed_router)) - log_link_debug(link, "Router %s is not in allow list, ignoring.", IN6_ADDR_TO_STRING(&router)); - else - log_link_debug(link, "Router %s is in deny list, ignoring.", IN6_ADDR_TO_STRING(&router)); - } + if (!set_isempty(link->network->ndisc_allow_listed_router)) + log_link_debug(link, "Router %s is not in allow list, ignoring.", IN6_ADDR_TO_STRING(&router)); + else + log_link_debug(link, "Router %s is in deny list, ignoring.", IN6_ADDR_TO_STRING(&router)); return 0; } diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index e29986c214..01c3a432a5 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1659,12 +1659,6 @@ static int verify_arguments(void) { SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_privileged); if (!arg_privileged) { - /* machined is not accessible to unpriv clients */ - if (arg_register) { - log_notice("Automatically implying --register=no, since machined is not accessible to unprivileged clients."); - arg_register = false; - } - if (!arg_private_network) { log_notice("Automatically implying --private-network, since mounting /sys/ in an unprivileged user namespaces requires network namespacing."); arg_private_network = true; @@ -5353,7 +5347,7 @@ static int run_container( } if (arg_register || !arg_keep_unit) { - if (arg_privileged) + if (arg_privileged || arg_register) r = sd_bus_default_system(&bus); else r = sd_bus_default_user(&bus); diff --git a/src/partition/repart.c b/src/partition/repart.c index bee48b770c..d8253089a1 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -295,6 +295,7 @@ typedef struct Partition { int copy_blocks_fd; uint64_t copy_blocks_offset; uint64_t copy_blocks_size; + uint64_t copy_blocks_done; char *format; char **copy_files; @@ -4499,6 +4500,26 @@ static int partition_format_verity_sig(Context *context, Partition *p) { return 0; } +static int progress_bytes(uint64_t n_bytes, void *userdata) { + Partition *p = ASSERT_PTR(userdata); + _cleanup_free_ char *s = NULL; + + p->copy_blocks_done += n_bytes; + + if (asprintf(&s, "%s %s %s %s/%s ", + strna(p->copy_blocks_path), + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + strna(p->definition_path), + FORMAT_BYTES(p->copy_blocks_done), + FORMAT_BYTES(p->copy_blocks_size)) < 0) + return log_oom(); + + draw_progress_bar(s, + p->copy_blocks_done >= p->copy_blocks_size ? 100.0 : /* catch division be zero */ + 100.0 * (double) p->copy_blocks_done / (double) p->copy_blocks_size); + return 0; +} + static int context_copy_blocks(Context *context) { int r; @@ -4522,8 +4543,13 @@ static int context_copy_blocks(Context *context) { continue; assert(p->new_size != UINT64_MAX); - assert(p->copy_blocks_size != UINT64_MAX); - assert(p->new_size >= p->copy_blocks_size + (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); + + size_t extra = p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0; + + if (p->copy_blocks_size == UINT64_MAX) + p->copy_blocks_size = LESS_BY(p->new_size, extra); + + assert(p->new_size >= p->copy_blocks_size + extra); usec_t start_timestamp = now(CLOCK_MONOTONIC); @@ -4550,7 +4576,8 @@ static int context_copy_blocks(Context *context) { return log_error_errno(errno, "Failed to seek to copy blocks offset in %s: %m", p->copy_blocks_path); } - r = copy_bytes(p->copy_blocks_fd, partition_target_fd(t), p->copy_blocks_size, COPY_REFLINK); + r = copy_bytes_full(p->copy_blocks_fd, partition_target_fd(t), p->copy_blocks_size, COPY_REFLINK, /* ret_remains= */ NULL, /* ret_remains_size= */ NULL, progress_bytes, p); + clear_progress_bar(/* prefix= */ NULL); if (r < 0) return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path); @@ -6350,13 +6377,17 @@ static int context_open_copy_block_paths( r = blockdev_get_device_size(source_fd, &size); if (r < 0) return log_error_errno(r, "Failed to determine size of block device to copy from: %m"); - } else + } else if (S_ISCHR(st.st_mode)) + size = UINT64_MAX; + else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path to copy blocks from '%s' is not a regular file, block device or directory, refusing.", opened); - if (size <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has zero size, refusing.", opened); - if (size % 512 != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has size that is not multiple of 512, refusing.", opened); + if (size != UINT64_MAX) { + if (size <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has zero size, refusing.", opened); + if (size % 512 != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has size that is not multiple of 512, refusing.", opened); + } p->copy_blocks_fd = TAKE_FD(source_fd); p->copy_blocks_size = size; diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 57b930d6cb..e4eb437b2e 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -26,9 +26,9 @@ #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" +#include "polkit-agent.h" #include "portable.h" #include "pretty-print.h" -#include "spawn-polkit-agent.h" #include "string-util.h" #include "strv.h" #include "terminal-util.h" diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 2eee418adf..9bbdc648fc 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -1165,7 +1165,6 @@ static int dns_question_to_json(DnsQuestion *q, sd_json_variant **ret) { int manager_monitor_send(Manager *m, DnsQuery *q) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *jquestion = NULL, *jcollected_questions = NULL, *janswer = NULL; _cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL; - Varlink *connection; DnsAnswerItem *rri; int r; @@ -1220,34 +1219,32 @@ int manager_monitor_send(Manager *m, DnsQuery *q) { return log_debug_errno(r, "Failed to append notification entry to array: %m"); } - SET_FOREACH(connection, m->varlink_subscription) { - r = varlink_notifybo( - connection, - SD_JSON_BUILD_PAIR("state", SD_JSON_BUILD_STRING(dns_transaction_state_to_string(q->state))), - SD_JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_DNSSEC_FAILED, - "result", SD_JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))), - SD_JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_RCODE_FAILURE, - "rcode", SD_JSON_BUILD_INTEGER(q->answer_rcode)), - SD_JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_ERRNO, - "errno", SD_JSON_BUILD_INTEGER(q->answer_errno)), - SD_JSON_BUILD_PAIR_CONDITION(IN_SET(q->state, - DNS_TRANSACTION_DNSSEC_FAILED, - DNS_TRANSACTION_RCODE_FAILURE) && - q->answer_ede_rcode >= 0, - "extendedDNSErrorCode", SD_JSON_BUILD_INTEGER(q->answer_ede_rcode)), - SD_JSON_BUILD_PAIR_CONDITION(IN_SET(q->state, - DNS_TRANSACTION_DNSSEC_FAILED, - DNS_TRANSACTION_RCODE_FAILURE) && - q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), - "extendedDNSErrorMessage", SD_JSON_BUILD_STRING(q->answer_ede_msg)), - SD_JSON_BUILD_PAIR("question", SD_JSON_BUILD_VARIANT(jquestion)), - SD_JSON_BUILD_PAIR_CONDITION(!!jcollected_questions, - "collectedQuestions", SD_JSON_BUILD_VARIANT(jcollected_questions)), - SD_JSON_BUILD_PAIR_CONDITION(!!janswer, + r = varlink_many_notifybo( + m->varlink_subscription, + SD_JSON_BUILD_PAIR("state", SD_JSON_BUILD_STRING(dns_transaction_state_to_string(q->state))), + SD_JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_DNSSEC_FAILED, + "result", SD_JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))), + SD_JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_RCODE_FAILURE, + "rcode", SD_JSON_BUILD_INTEGER(q->answer_rcode)), + SD_JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_ERRNO, + "errno", SD_JSON_BUILD_INTEGER(q->answer_errno)), + SD_JSON_BUILD_PAIR_CONDITION(IN_SET(q->state, + DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_RCODE_FAILURE) && + q->answer_ede_rcode >= 0, + "extendedDNSErrorCode", SD_JSON_BUILD_INTEGER(q->answer_ede_rcode)), + SD_JSON_BUILD_PAIR_CONDITION(IN_SET(q->state, + DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_RCODE_FAILURE) && + q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), + "extendedDNSErrorMessage", SD_JSON_BUILD_STRING(q->answer_ede_msg)), + SD_JSON_BUILD_PAIR("question", SD_JSON_BUILD_VARIANT(jquestion)), + SD_JSON_BUILD_PAIR_CONDITION(!!jcollected_questions, + "collectedQuestions", SD_JSON_BUILD_VARIANT(jcollected_questions)), + SD_JSON_BUILD_PAIR_CONDITION(!!janswer, "answer", SD_JSON_BUILD_VARIANT(janswer))); - if (r < 0) - log_debug_errno(r, "Failed to send monitor event, ignoring: %m"); - } + if (r < 0) + log_debug_errno(r, "Failed to send monitor event, ignoring: %m"); return 0; } diff --git a/src/run/run.c b/src/run/run.c index cb67a45b26..6565336866 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -30,11 +30,11 @@ #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "process-util.h" #include "ptyfwd.h" #include "signal-util.h" -#include "spawn-polkit-agent.h" #include "special.h" #include "strv.h" #include "terminal-util.h" diff --git a/src/shared/spawn-ask-password-agent.c b/src/shared/ask-password-agent.c index d34cfffa83..75eaa46254 100644 --- a/src/shared/spawn-ask-password-agent.c +++ b/src/shared/ask-password-agent.c @@ -4,10 +4,10 @@ #include <stdlib.h> #include <unistd.h> +#include "ask-password-agent.h" #include "exec-util.h" #include "log.h" #include "process-util.h" -#include "spawn-ask-password-agent.h" static pid_t agent_pid = 0; diff --git a/src/shared/spawn-ask-password-agent.h b/src/shared/ask-password-agent.h index a76cdb11fe..a76cdb11fe 100644 --- a/src/shared/spawn-ask-password-agent.h +++ b/src/shared/ask-password-agent.h diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c index aefc84a00c..00c55463c8 100644 --- a/src/shared/bus-polkit.c +++ b/src/shared/bus-polkit.c @@ -786,11 +786,13 @@ int varlink_verify_polkit_async_full( if (r != 0) log_debug("Found matching previous polkit authentication for '%s'.", action); if (r < 0) { - /* Reply with a nice error */ - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED)) - (void) varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL); - else if (ERRNO_IS_NEG_PRIVILEGE(r)) - (void) varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL); + if (!FLAGS_SET(flags, POLKIT_DONT_REPLY)) { + /* Reply with a nice error */ + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED)) + (void) varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL); + else if (ERRNO_IS_NEG_PRIVILEGE(r)) + (void) varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL); + } return r; } diff --git a/src/shared/bus-polkit.h b/src/shared/bus-polkit.h index 25616a0a45..ba83cedbe1 100644 --- a/src/shared/bus-polkit.h +++ b/src/shared/bus-polkit.h @@ -11,6 +11,7 @@ typedef enum PolkitFLags { POLKIT_ALLOW_INTERACTIVE = 1 << 0, /* Allow interactive auth (typically not required, because can be derived from bus message/link automatically) */ POLKIT_ALWAYS_QUERY = 1 << 1, /* Query polkit even if client is privileged */ POLKIT_DEFAULT_ALLOW = 1 << 2, /* If polkit is not around, assume "allow" rather than the usual "deny" */ + POLKIT_DONT_REPLY = 1 << 3, /* Varlink: don't immediately propagate polkit error to the Varlink client */ } PolkitFlags; int bus_test_polkit(sd_bus_message *call, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index da83422524..751cb29c62 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1037,6 +1037,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con "SyslogIdentifier", "ProtectSystem", "ProtectHome", + "PrivateTmpEx", "SELinuxContext", "RootImage", "RootVerity", diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index fcc45c6e25..5a5a3c7613 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -9,6 +9,7 @@ #include "alloc-util.h" #include "chase.h" +#include "calendarspec.h" #include "conf-files.h" #include "conf-parser.h" #include "constants.h" @@ -171,7 +172,7 @@ static int next_assignment( /* Parse a single logical line */ static int parse_line( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *sections, @@ -868,7 +869,7 @@ DEFINE_PARSER(mode, mode_t, parse_mode); DEFINE_PARSER(pid, pid_t, parse_pid); int config_parse_iec_size( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *section, @@ -900,7 +901,7 @@ int config_parse_iec_size( } int config_parse_si_uint64( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *section, @@ -926,7 +927,7 @@ int config_parse_si_uint64( } int config_parse_iec_uint64( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *section, @@ -946,13 +947,13 @@ int config_parse_iec_uint64( r = parse_size(rvalue, 1024, bytes); if (r < 0) - log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse size value '%s', ignoring: %m", rvalue); return 0; } int config_parse_iec_uint64_infinity( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *section, @@ -976,7 +977,7 @@ int config_parse_iec_uint64_infinity( } int config_parse_bool( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *section, @@ -1036,7 +1037,7 @@ int config_parse_id128( } int config_parse_tristate( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *section, @@ -1636,7 +1637,7 @@ int config_parse_rlimit( } int config_parse_permille( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *section, @@ -1667,7 +1668,7 @@ int config_parse_permille( } int config_parse_vlanprotocol( - const char* unit, + const char *unit, const char *filename, unsigned line, const char *section, @@ -1980,6 +1981,41 @@ int config_parse_unsigned_bounded( return r; } +int config_parse_calendar( + 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) { + + CalendarSpec **cr = data; + _cleanup_(calendar_spec_freep) CalendarSpec *c = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + *cr = calendar_spec_free(*cr); + return 0; + } + + r = calendar_spec_from_string(rvalue, &c); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse calendar specification, ignoring: %s", rvalue); + else + *cr = TAKE_PTR(c); + + return 0; +} + DEFINE_CONFIG_PARSE(config_parse_percent, parse_percent, "Failed to parse percent value"); DEFINE_CONFIG_PARSE(config_parse_permyriad, parse_permyriad, "Failed to parse permyriad value"); DEFINE_CONFIG_PARSE_PTR(config_parse_sec_fix_0, parse_sec_fix_0, usec_t, "Failed to parse time value"); diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index 35e203cb12..f1860db448 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -251,6 +251,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_permyriad); CONFIG_PARSER_PROTOTYPE(config_parse_pid); CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0); CONFIG_PARSER_PROTOTYPE(config_parse_timezone); +CONFIG_PARSER_PROTOTYPE(config_parse_calendar); typedef enum Disabled { DISABLED_CONFIGURATION, diff --git a/src/shared/cryptsetup-fido2.c b/src/shared/cryptsetup-fido2.c index ebb1c65216..8a5d42baba 100644 --- a/src/shared/cryptsetup-fido2.c +++ b/src/shared/cryptsetup-fido2.c @@ -5,8 +5,10 @@ #include "ask-password-api.h" #include "cryptsetup-fido2.h" #include "env-util.h" +#include "fido2-util.h" #include "fileio.h" #include "hexdecoct.h" +#include "iovec-util.h" #include "libfido2-util.h" #include "parse-util.h" #include "random-util.h" @@ -33,38 +35,29 @@ int acquire_fido2_key( _cleanup_(erase_and_freep) char *envpw = NULL; _cleanup_strv_free_erase_ char **pins = NULL; - _cleanup_free_ void *loaded_salt = NULL; + _cleanup_(iovec_done_erase) struct iovec loaded_salt = {}; bool device_exists = false; - const char *salt; - size_t salt_size; + struct iovec salt; int r; if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Local verification is required to unlock this volume, but the 'headless' parameter was set."); - askpw_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; - assert(cid); assert(key_file || key_data); - if (key_data) { - salt = key_data; - salt_size = key_data_size; - } else { - _cleanup_free_ char *bindname = NULL; - - /* If we read the salt via AF_UNIX, make this client recognizable */ - if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-fido2/%s", random_u64(), volume_name) < 0) - return log_oom(); - - r = read_full_file_full( - AT_FDCWD, key_file, - key_file_offset == 0 ? UINT64_MAX : key_file_offset, - key_file_size == 0 ? SIZE_MAX : key_file_size, - READ_FULL_FILE_CONNECT_SOCKET, - bindname, - (char**) &loaded_salt, &salt_size); + if (key_data) + salt = IOVEC_MAKE(key_data, key_data_size); + else { + if (key_file_size > 0) + log_debug("Ignoring 'keyfile-size=' option for a FIDO2 salt file."); + + r = fido2_read_salt_file( + key_file, key_file_offset, + /* client= */ "cryptsetup", + /* node= */ volume_name, + &loaded_salt); if (r < 0) return r; @@ -102,7 +95,7 @@ int acquire_fido2_key( r = fido2_use_hmac_hash( device, rp_id ?: "io.systemd.cryptsetup", - salt, salt_size, + salt.iov_base, salt.iov_len, cid, cid_size, pins, required, diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c index d029f101ad..95c01678aa 100644 --- a/src/shared/cryptsetup-tpm2.c +++ b/src/shared/cryptsetup-tpm2.c @@ -178,6 +178,8 @@ int acquire_tpm2_key( if (r < 0) return r; + askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + if (iovec_is_set(salt)) { uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; CLEANUP_ERASE(salted_pin); diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index 79ff4f8116..49492330e2 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -791,7 +791,8 @@ int parse_elf_object(int fd, const char *executable, bool fork_disable_dump, cha NULL); if (r < 0) { if (r == -EPROTO) { /* We should have the errno from the child, but don't clobber original error */ - int e, k; + ssize_t k; + int e; k = read(error_pipe[0], &e, sizeof(e)); if (k < 0 && errno != EAGAIN) /* Pipe is non-blocking, EAGAIN means there's nothing */ diff --git a/src/shared/fido2-util.c b/src/shared/fido2-util.c new file mode 100644 index 0000000000..1dc57cbd42 --- /dev/null +++ b/src/shared/fido2-util.c @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fido2-util.h" +#include "fileio.h" +#include "libfido2-util.h" +#include "random-util.h" + +int fido2_generate_salt(struct iovec *ret_salt) { + _cleanup_(iovec_done) struct iovec salt = {}; + int r; + + r = crypto_random_bytes_allocate_iovec(FIDO2_SALT_SIZE, &salt); + if (r < 0) + return log_error_errno(r, "Failed to generate FIDO2 salt: %m"); + + *ret_salt = TAKE_STRUCT(salt); + return 0; +} + +int fido2_read_salt_file(const char *filename, uint64_t offset, const char *client, const char *node, struct iovec *ret_salt) { + _cleanup_(iovec_done_erase) struct iovec salt = {}; + _cleanup_free_ char *bind_name = NULL; + int r; + + /* If we read the salt via AF_UNIX, make the client recognizable */ + if (asprintf(&bind_name, "@%" PRIx64"/%s-fido2-salt/%s", random_u64(), client, node) < 0) + return log_oom(); + + r = read_full_file_full( + AT_FDCWD, filename, + offset == 0 ? UINT64_MAX : offset, + /* size= */ FIDO2_SALT_SIZE, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE| + READ_FULL_FILE_CONNECT_SOCKET|READ_FULL_FILE_FAIL_WHEN_LARGER, + bind_name, (char**) &salt.iov_base, &salt.iov_len); + if (r == -E2BIG || (r >= 0 && salt.iov_len != FIDO2_SALT_SIZE)) + return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), + "FIDO2 salt file must contain exactly %u bytes.", FIDO2_SALT_SIZE); + if (r < 0) + return log_error_errno(r, "Reading FIDO2 salt file '%s' failed: %m", filename); + + *ret_salt = TAKE_STRUCT(salt); + return 0; +} diff --git a/src/shared/fido2-util.h b/src/shared/fido2-util.h new file mode 100644 index 0000000000..73f39b43ca --- /dev/null +++ b/src/shared/fido2-util.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdint.h> + +#include "iovec-util.h" + +int fido2_generate_salt(struct iovec *ret_salt); +int fido2_read_salt_file(const char *filename, uint64_t offset, const char *client, const char *node, struct iovec *ret_salt); diff --git a/src/shared/import-util.c b/src/shared/import-util.c index 9057b78d28..bcdc21f79e 100644 --- a/src/shared/import-util.c +++ b/src/shared/import-util.c @@ -126,9 +126,16 @@ int import_url_change_suffix( return 0; } +static const char* const import_type_table[_IMPORT_TYPE_MAX] = { + [IMPORT_RAW] = "raw", + [IMPORT_TAR] = "tar", +}; + +DEFINE_STRING_TABLE_LOOKUP(import_type, ImportType); + static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = { - [IMPORT_VERIFY_NO] = "no", - [IMPORT_VERIFY_CHECKSUM] = "checksum", + [IMPORT_VERIFY_NO] = "no", + [IMPORT_VERIFY_CHECKSUM] = "checksum", [IMPORT_VERIFY_SIGNATURE] = "signature", }; diff --git a/src/shared/import-util.h b/src/shared/import-util.h index 3b2425b916..98b99e371f 100644 --- a/src/shared/import-util.h +++ b/src/shared/import-util.h @@ -5,6 +5,13 @@ #include "macro.h" +typedef enum ImportType { + IMPORT_RAW, + IMPORT_TAR, + _IMPORT_TYPE_MAX, + _IMPORT_TYPE_INVALID = -EINVAL, +} ImportType; + typedef enum ImportVerify { IMPORT_VERIFY_NO, IMPORT_VERIFY_CHECKSUM, @@ -25,6 +32,9 @@ static inline int import_url_append_component(const char *url, const char *suffi return import_url_change_suffix(url, 0, suffix, ret); } +const char* import_type_to_string(ImportType v) _const_; +ImportType import_type_from_string(const char *s) _pure_; + const char* import_verify_to_string(ImportVerify v) _const_; ImportVerify import_verify_from_string(const char *s) _pure_; diff --git a/src/shared/install.c b/src/shared/install.c index ca8e7d2733..08c2915fb5 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -1810,7 +1810,8 @@ static int install_info_discover( r = install_info_traverse(ctx, lp, info, flags, ret); if (r < 0) - install_changes_add(changes, n_changes, r, name_or_path, NULL); + return install_changes_add(changes, n_changes, r, name_or_path, NULL); + return r; } @@ -1871,7 +1872,10 @@ int unit_file_verify_alias( if (!p) p = endswith(dir, ".requires"); if (!p) { - install_changes_add(changes, n_changes, -EXDEV, dst, NULL); + r = install_changes_add(changes, n_changes, -EXDEV, dst, NULL); + if (r != -EXDEV) + return r; + return log_debug_errno(SYNTHETIC_ERRNO(EXDEV), "Invalid path \"%s\" in alias.", dir); } @@ -1879,7 +1883,9 @@ int unit_file_verify_alias( UnitNameFlags type = unit_name_classify(dir); if (type < 0) { - install_changes_add(changes, n_changes, -EXDEV, dst, NULL); + r = install_changes_add(changes, n_changes, -EXDEV, dst, NULL); + if (r != -EXDEV) + return r; return log_debug_errno(SYNTHETIC_ERRNO(EXDEV), "Invalid unit name component \"%s\" in alias.", dir); } @@ -1891,7 +1897,10 @@ int unit_file_verify_alias( if (r < 0) return log_error_errno(r, "Failed to verify alias validity: %m"); if (r == 0) { - install_changes_add(changes, n_changes, -EXDEV, dst, info->name); + r = install_changes_add(changes, n_changes, -EXDEV, dst, info->name); + if (r != -EXDEV) + return r; + return log_debug_errno(SYNTHETIC_ERRNO(EXDEV), "Invalid unit \"%s\" symlink \"%s\".", info->name, dst); @@ -1905,7 +1914,9 @@ int unit_file_verify_alias( UnitNameFlags type = unit_name_to_instance(info->name, &inst); if (type < 0) { - install_changes_add(changes, n_changes, -EUCLEAN, info->name, NULL); + r = install_changes_add(changes, n_changes, -EUCLEAN, info->name, NULL); + if (r != -EUCLEAN) + return r; return log_debug_errno(type, "Failed to extract instance name from \"%s\": %m", info->name); } @@ -2288,10 +2299,14 @@ static int install_context_mark_for_removal( } } else if (r < 0) { log_debug_errno(r, "Failed to find unit %s, removing name: %m", i->name); - install_changes_add(changes, n_changes, r, i->path ?: i->name, NULL); + int k = install_changes_add(changes, n_changes, r, i->path ?: i->name, NULL); + if (k != r) + return k; } else if (i->install_mode == INSTALL_MODE_MASKED) { log_debug("Unit file %s is masked, ignoring.", i->name); - install_changes_add(changes, n_changes, INSTALL_CHANGE_IS_MASKED, i->path ?: i->name, NULL); + r = install_changes_add(changes, n_changes, INSTALL_CHANGE_IS_MASKED, i->path ?: i->name, NULL); + if (r < 0) + return r; continue; } else if (i->install_mode != INSTALL_MODE_REGULAR) { log_debug("Unit %s has install mode %s, ignoring.", @@ -2439,10 +2454,8 @@ int unit_file_unmask( return -ENOMEM; if (!dry_run && unlink(path) < 0) { - if (errno != ENOENT) { - RET_GATHER(r, -errno); - install_changes_add(changes, n_changes, -errno, path, NULL); - } + if (errno != ENOENT) + RET_GATHER(r, install_changes_add(changes, n_changes, -errno, path, NULL)); continue; } diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c index bb0536e48a..4dc0b8f662 100644 --- a/src/shared/journal-importer.c +++ b/src/shared/journal-importer.c @@ -127,7 +127,7 @@ static int fill_fixed_size(JournalImporter *imp, void **data, size_t size) { assert(data); while (imp->filled - imp->offset < size) { - int n; + ssize_t n; if (imp->passive_fd) /* we have to wait for some data to come to us */ diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 74f52644a6..d19018b331 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -10,7 +10,6 @@ #include "glyph-util.h" #include "log.h" #include "memory-util.h" -#include "random-util.h" #include "strv.h" #include "unistd.h" @@ -683,8 +682,6 @@ finish: return r; } -#define FIDO2_SALT_SIZE 32 - int fido2_generate_hmac_hash( const char *device, const char *rp_id, @@ -697,13 +694,13 @@ int fido2_generate_hmac_hash( const char *askpw_credential, Fido2EnrollFlags lock_with, int cred_alg, + const struct iovec *salt, void **ret_cid, size_t *ret_cid_size, - void **ret_salt, size_t *ret_salt_size, void **ret_secret, size_t *ret_secret_size, char **ret_usedpin, Fido2EnrollFlags *ret_locked_with) { - _cleanup_(erase_and_freep) void *salt = NULL, *secret_copy = NULL; + _cleanup_(erase_and_freep) void *secret_copy = NULL; _cleanup_(fido_assert_free_wrapper) fido_assert_t *a = NULL; _cleanup_(fido_cred_free_wrapper) fido_cred_t *c = NULL; _cleanup_(fido_dev_free_wrapper) fido_dev_t *d = NULL; @@ -717,12 +714,10 @@ int fido2_generate_hmac_hash( assert(device); assert(ret_cid); assert(ret_cid_size); - assert(ret_salt); - assert(ret_salt_size); assert(ret_secret); assert(ret_secret_size); - /* Construction is like this: we generate a salt of 32 bytes. We then ask the FIDO2 device to + /* Construction is like this: we read or generate a salt of 32 bytes. We then ask the FIDO2 device to * HMAC-SHA256 it for us with its internal key. The result is the key used by LUKS and account * authentication. LUKS and UNIX password auth all do their own salting before hashing, so that FIDO2 * device never sees the volume key. @@ -731,25 +726,18 @@ int fido2_generate_hmac_hash( * * with: S → LUKS/account authentication key (never stored) * I → internal key on FIDO2 device (stored in the FIDO2 device) - * D → salt we generate here (stored in the privileged part of the JSON record) + * D → salt (stored in the privileged part of the JSON record or read from a file/socket) * */ assert(device); assert((lock_with & ~(FIDO2ENROLL_PIN|FIDO2ENROLL_UP|FIDO2ENROLL_UV)) == 0); + assert(iovec_is_set(salt)); r = dlopen_libfido2(); if (r < 0) return log_error_errno(r, "FIDO2 token support is not installed."); - salt = malloc(FIDO2_SALT_SIZE); - if (!salt) - return log_oom(); - - r = crypto_random_bytes(salt, FIDO2_SALT_SIZE); - if (r < 0) - return log_error_errno(r, "Failed to generate salt: %m"); - d = sym_fido_dev_new(); if (!d) return log_oom(); @@ -935,7 +923,7 @@ int fido2_generate_hmac_hash( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to enable HMAC-SECRET extension on FIDO2 assertion: %s", sym_fido_strerr(r)); - r = sym_fido_assert_set_hmac_salt(a, salt, FIDO2_SALT_SIZE); + r = sym_fido_assert_set_hmac_salt(a, salt->iov_base, salt->iov_len); if (r != FIDO_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set salt on FIDO2 assertion: %s", sym_fido_strerr(r)); @@ -1072,8 +1060,6 @@ int fido2_generate_hmac_hash( *ret_cid = TAKE_PTR(cid_copy); *ret_cid_size = cid_size; - *ret_salt = TAKE_PTR(salt); - *ret_salt_size = FIDO2_SALT_SIZE; *ret_secret = TAKE_PTR(secret_copy); *ret_secret_size = secret_size; diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index af2a4e7006..c7ed6bac34 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -1,8 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "iovec-util.h" #include "macro.h" +#define FIDO2_SALT_SIZE 32U + typedef enum Fido2EnrollFlags { FIDO2ENROLL_PIN = 1 << 0, FIDO2ENROLL_UP = 1 << 1, /* User presence (ie: touching token) */ @@ -116,8 +119,8 @@ int fido2_generate_hmac_hash( const char *askpw_credential, Fido2EnrollFlags lock_with, int cred_alg, + const struct iovec *salt, void **ret_cid, size_t *ret_cid_size, - void **ret_salt, size_t *ret_salt_size, void **ret_secret, size_t *ret_secret_size, char **ret_usedpin, Fido2EnrollFlags *ret_locked_with); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 34f9411fcf..7dcc77e114 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -443,7 +443,6 @@ static void parse_display_realtime( sd_journal *j, const char *source_realtime, const char *source_monotonic, - const char *source_boottime, usec_t *ret) { usec_t t, s, u; @@ -451,10 +450,8 @@ static void parse_display_realtime( assert(j); assert(ret); - if (!source_boottime) - /* _SOURCE_MONOTONIC_TIMESTAMP field is usable only when _SOURCE_BOOTTIME_TIMESTAMP exists, - * as previously the timestamp was in CLOCK_BOOTTIME. */ - source_monotonic = NULL; + // FIXME: _SOURCE_MONOTONIC_TIMESTAMP is in CLOCK_BOOTTIME, hence we cannot use it for adjusting realtime. + source_monotonic = NULL; /* First, try _SOURCE_REALTIME_TIMESTAMP. */ if (source_realtime && safe_atou64(source_realtime, &t) >= 0 && VALID_REALTIME(t)) { @@ -483,7 +480,6 @@ static void parse_display_timestamp( sd_journal *j, const char *source_realtime, const char *source_monotonic, - const char *source_boottime, dual_timestamp *ret_display_ts, sd_id128_t *ret_boot_id) { @@ -495,10 +491,8 @@ static void parse_display_timestamp( assert(ret_display_ts); assert(ret_boot_id); - if (!source_boottime) - /* _SOURCE_MONOTONIC_TIMESTAMP field is usable only when _SOURCE_BOOTTIME_TIMESTAMP exists, - * as previously the timestamp was in CLOCK_BOOTTIME. */ - source_monotonic = NULL; + // FIXME: _SOURCE_MONOTONIC_TIMESTAMP is in CLOCK_BOOTTIME, hence we cannot use it for adjusting realtime. + source_monotonic = NULL; if (source_realtime && safe_atou64(source_realtime, &t) >= 0 && VALID_REALTIME(t)) source_ts.realtime = t; @@ -539,7 +533,7 @@ static int output_short( _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *priority = NULL, *transport = NULL, *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL, - *realtime = NULL, *monotonic = NULL, *boottime = NULL; + *realtime = NULL, *monotonic = NULL; size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, priority_len = 0, transport_len = 0, config_file_len = 0, unit_len = 0, user_unit_len = 0, documentation_url_len = 0; @@ -562,7 +556,6 @@ static int output_short( PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len), PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, NULL ), PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL ), - PARSE_FIELD_VEC_ENTRY("_SOURCE_BOOTTIME_TIMESTAMP=", &boottime, NULL ), }; size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0}; @@ -609,11 +602,11 @@ static int output_short( audit = streq_ptr(transport, "audit"); if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) { - parse_display_timestamp(j, realtime, monotonic, boottime, &display_ts, &boot_id); + parse_display_timestamp(j, realtime, monotonic, &display_ts, &boot_id); r = output_timestamp_monotonic(f, mode, &display_ts, &boot_id, previous_display_ts, previous_boot_id); } else { usec_t usec; - parse_display_realtime(j, realtime, monotonic, boottime, &usec); + parse_display_realtime(j, realtime, monotonic, &usec); r = output_timestamp_realtime(f, j, mode, flags, usec); } if (r < 0) @@ -746,12 +739,11 @@ static int output_short( static int get_display_realtime(sd_journal *j, usec_t *ret) { const void *data; - _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *boottime = NULL; + _cleanup_free_ char *realtime = NULL, *monotonic = NULL; size_t length; const ParseFieldVec message_fields[] = { PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, NULL), PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL), - PARSE_FIELD_VEC_ENTRY("_SOURCE_BOOTTIME_TIMESTAMP=", &boottime, NULL), }; int r; @@ -763,13 +755,13 @@ static int get_display_realtime(sd_journal *j, usec_t *ret) { if (r < 0) return r; - if (realtime && monotonic && boottime) + if (realtime && monotonic) break; } if (r < 0) return r; - (void) parse_display_realtime(j, realtime, monotonic, boottime, ret); + (void) parse_display_realtime(j, realtime, monotonic, ret); /* Restart all data before */ sd_journal_restart_data(j); diff --git a/src/shared/main-func.h b/src/shared/main-func.h index d0689b42d9..67537555a1 100644 --- a/src/shared/main-func.h +++ b/src/shared/main-func.h @@ -10,12 +10,12 @@ #include "sd-daemon.h" #include "argv-util.h" +#include "ask-password-agent.h" #include "hashmap.h" #include "pager.h" +#include "polkit-agent.h" #include "selinux-util.h" #include "signal-util.h" -#include "spawn-ask-password-agent.h" -#include "spawn-polkit-agent.h" #include "static-destruct.h" #define _DEFINE_MAIN_FUNCTION(intro, impl, result_to_exit_status, result_to_return_value) \ diff --git a/src/shared/meson.build b/src/shared/meson.build index 5eef659d1f..eac5b83972 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -4,6 +4,7 @@ shared_sources = files( 'acl-util.c', 'acpi-fpdt.c', 'apparmor-util.c', + 'ask-password-agent.c', 'ask-password-api.c', 'async.c', 'barrier.c', @@ -69,6 +70,7 @@ shared_sources = files( 'exit-status.c', 'extension-util.c', 'fdset.c', + 'fido2-util.c', 'fileio-label.c', 'find-esp.c', 'firewall-util-nft.c', @@ -140,6 +142,7 @@ shared_sources = files( 'pe-binary.c', 'pkcs11-util.c', 'plymouth-util.c', + 'polkit-agent.c', 'pretty-print.c', 'capsule-util.c', 'ptyfwd.c', @@ -159,8 +162,6 @@ shared_sources = files( 'smbios11.c', 'socket-label.c', 'socket-netlink.c', - 'spawn-ask-password-agent.c', - 'spawn-polkit-agent.c', 'specifier.c', 'switch-root.c', 'tmpfile-util-label.c', @@ -179,6 +180,7 @@ shared_sources = files( 'varlink-io.systemd.BootControl.c', 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.Hostname.c', + 'varlink-io.systemd.Import.c', 'varlink-io.systemd.Journal.c', 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.ManagedOOM.c', diff --git a/src/shared/spawn-polkit-agent.c b/src/shared/polkit-agent.c index ce3c5fb948..a891246156 100644 --- a/src/shared/spawn-polkit-agent.c +++ b/src/shared/polkit-agent.c @@ -11,8 +11,8 @@ #include "io-util.h" #include "log.h" #include "macro.h" +#include "polkit-agent.h" #include "process-util.h" -#include "spawn-polkit-agent.h" #include "stdio-util.h" #include "time-util.h" diff --git a/src/shared/spawn-polkit-agent.h b/src/shared/polkit-agent.h index 325dfdde46..325dfdde46 100644 --- a/src/shared/spawn-polkit-agent.h +++ b/src/shared/polkit-agent.h diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 8eba052f2c..f361c4760a 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -532,7 +532,9 @@ void clear_progress_bar(const char *prefix) { fputc('\r', stderr); if (terminal_is_dumb()) - fputs(strrepa(" ", utf8_console_width(prefix) + 4), /* 4: %3.0f%% */ + fputs(strrepa(" ", + prefix ? utf8_console_width(prefix) + 4 : + LESS_BY(columns(), 1U)), /* 4: %3.0f%% */ stderr); else fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 2469e24253..d31d6b494b 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -2030,39 +2030,43 @@ int parse_syscall_archs(char **l, Set **ret_archs) { return 0; } -int seccomp_filter_set_add(Hashmap *filter, bool add, const SyscallFilterSet *set) { - int r; +int seccomp_filter_set_add_by_name(Hashmap *filter, bool add, const char *name) { + assert(filter); + assert(name); - assert(set); + if (name[0] == '@') { + const SyscallFilterSet *more; - NULSTR_FOREACH(i, set->value) { + more = syscall_filter_set_find(name); + if (!more) + return -ENXIO; - if (i[0] == '@') { - const SyscallFilterSet *more; + return seccomp_filter_set_add(filter, add, more); + } - more = syscall_filter_set_find(i); - if (!more) - return -ENXIO; + int id = seccomp_syscall_resolve_name(name); + if (id == __NR_SCMP_ERROR) { + log_debug("System call %s is not known, ignoring.", name); + return 0; + } - r = seccomp_filter_set_add(filter, add, more); - if (r < 0) - return r; - } else { - int id; + if (add) + return hashmap_put(filter, INT_TO_PTR(id + 1), INT_TO_PTR(-1)); - id = seccomp_syscall_resolve_name(i); - if (id == __NR_SCMP_ERROR) { - log_debug("System call %s is not known, ignoring.", i); - continue; - } + (void) hashmap_remove(filter, INT_TO_PTR(id + 1)); + return 0; +} - if (add) { - r = hashmap_put(filter, INT_TO_PTR(id + 1), INT_TO_PTR(-1)); - if (r < 0) - return r; - } else - (void) hashmap_remove(filter, INT_TO_PTR(id + 1)); - } +int seccomp_filter_set_add(Hashmap *filter, bool add, const SyscallFilterSet *set) { + int r; + + assert(filter); + assert(set); + + NULSTR_FOREACH(i, set->value) { + r = seccomp_filter_set_add_by_name(filter, add, i); + if (r < 0) + return r; } return 0; diff --git a/src/shared/seccomp-util.h b/src/shared/seccomp-util.h index fbf8555669..64deb5fd5d 100644 --- a/src/shared/seccomp-util.h +++ b/src/shared/seccomp-util.h @@ -70,6 +70,7 @@ extern const SyscallFilterSet syscall_filter_sets[]; const SyscallFilterSet *syscall_filter_set_find(const char *name); +int seccomp_filter_set_add_by_name(Hashmap *s, bool b, const char *name); int seccomp_filter_set_add(Hashmap *s, bool b, const SyscallFilterSet *set); int seccomp_add_syscall_filter_item( diff --git a/src/shared/varlink-idl.c b/src/shared/varlink-idl.c index 57765b18c2..e6a4942ad8 100644 --- a/src/shared/varlink-idl.c +++ b/src/shared/varlink-idl.c @@ -1452,6 +1452,28 @@ static bool varlink_idl_comment_is_valid(const char *comment) { return utf8_is_valid(comment); } +int varlink_idl_qualified_symbol_name_is_valid(const char *name) { + const char *dot; + + /* Validates a qualified symbol name (i.e. interface name, followed by a dot, followed by a symbol name) */ + + if (!name) + return false; + + dot = strrchr(name, '.'); + if (!dot) + return false; + + if (!varlink_idl_symbol_name_is_valid(dot + 1)) + return false; + + _cleanup_free_ char *iface = strndup(name, dot - name); + if (!iface) + return -ENOMEM; + + return varlink_idl_interface_name_is_valid(iface); +} + static int varlink_idl_symbol_consistent(const VarlinkInterface *interface, const VarlinkSymbol *symbol, int level); static int varlink_idl_field_consistent( @@ -1760,6 +1782,9 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, sd_json_vari for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) { + if (field->field_type == _VARLINK_FIELD_COMMENT) + continue; + assert(field->field_type == VARLINK_ENUM_VALUE); if (streq_ptr(field->name, s)) { @@ -1788,6 +1813,9 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, sd_json_vari for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) { + if (field->field_type == _VARLINK_FIELD_COMMENT) + continue; + if (field->field_direction != direction) continue; diff --git a/src/shared/varlink-idl.h b/src/shared/varlink-idl.h index 03a1a2b2e4..ffb55f3066 100644 --- a/src/shared/varlink-idl.h +++ b/src/shared/varlink-idl.h @@ -170,6 +170,8 @@ bool varlink_idl_field_name_is_valid(const char *name); bool varlink_idl_symbol_name_is_valid(const char *name); bool varlink_idl_interface_name_is_valid(const char *name); +int varlink_idl_qualified_symbol_name_is_valid(const char *name); + int varlink_idl_consistent(const VarlinkInterface *interface, int level); const VarlinkSymbol* varlink_idl_find_symbol(const VarlinkInterface *interface, VarlinkSymbolType type, const char *name); diff --git a/src/shared/varlink-io.systemd.Hostname.c b/src/shared/varlink-io.systemd.Hostname.c index a6c6aec2a8..247bca6da3 100644 --- a/src/shared/varlink-io.systemd.Hostname.c +++ b/src/shared/varlink-io.systemd.Hostname.c @@ -4,6 +4,7 @@ static VARLINK_DEFINE_METHOD( Describe, + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT(Hostname, VARLINK_STRING, 0), VARLINK_DEFINE_OUTPUT(StaticHostname, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT(PrettyHostname, VARLINK_STRING, VARLINK_NULLABLE), diff --git a/src/shared/varlink-io.systemd.Import.c b/src/shared/varlink-io.systemd.Import.c new file mode 100644 index 0000000000..4b2f3dcdea --- /dev/null +++ b/src/shared/varlink-io.systemd.Import.c @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Import.h" + +static VARLINK_DEFINE_ENUM_TYPE( + ImageClass, + VARLINK_FIELD_COMMENT("An image to boot as a system on baremetal, in a VM or as a container"), + VARLINK_DEFINE_ENUM_VALUE(machine), + VARLINK_FIELD_COMMENT("An portable service image"), + VARLINK_DEFINE_ENUM_VALUE(portable), + VARLINK_FIELD_COMMENT("A system extension image"), + VARLINK_DEFINE_ENUM_VALUE(sysext), + VARLINK_FIELD_COMMENT("A configuration extension image"), + VARLINK_DEFINE_ENUM_VALUE(confext)); + +static VARLINK_DEFINE_ENUM_TYPE( + RemoteType, + VARLINK_FIELD_COMMENT("Raw binary disk images, typically in a GPT envelope"), + VARLINK_DEFINE_ENUM_VALUE(raw), + VARLINK_FIELD_COMMENT("A tarball, optionally compressed"), + VARLINK_DEFINE_ENUM_VALUE(tar)); + +static VARLINK_DEFINE_ENUM_TYPE( + TransferType, + VARLINK_FIELD_COMMENT("A local import of a tarball"), + VARLINK_DEFINE_ENUM_VALUE(import_tar), + VARLINK_FIELD_COMMENT("A local import of a raw disk image"), + VARLINK_DEFINE_ENUM_VALUE(import_raw), + VARLINK_FIELD_COMMENT("A local import of a file system tree"), + VARLINK_DEFINE_ENUM_VALUE(import_fs), + VARLINK_FIELD_COMMENT("A local export of a tarball"), + VARLINK_DEFINE_ENUM_VALUE(export_tar), + VARLINK_FIELD_COMMENT("A local export of a raw disk image"), + VARLINK_DEFINE_ENUM_VALUE(export_raw), + VARLINK_FIELD_COMMENT("A download of a tarball"), + VARLINK_DEFINE_ENUM_VALUE(pull_tar), + VARLINK_FIELD_COMMENT("A download of a raw disk image"), + VARLINK_DEFINE_ENUM_VALUE(pull_raw)); + +static VARLINK_DEFINE_ENUM_TYPE( + ImageVerify, + VARLINK_FIELD_COMMENT("No verification"), + VARLINK_DEFINE_ENUM_VALUE(no), + VARLINK_FIELD_COMMENT("Verify that downloads match checksum file (SHA256SUMS), but do not check signature of checksum file"), + VARLINK_DEFINE_ENUM_VALUE(checksum), + VARLINK_FIELD_COMMENT("Verify that downloads match checksum file (SHA256SUMS), and check signature of checksum file."), + VARLINK_DEFINE_ENUM_VALUE(signature)); + +static VARLINK_DEFINE_STRUCT_TYPE( + LogMessage, + VARLINK_FIELD_COMMENT("The log message"), + VARLINK_DEFINE_FIELD(message, VARLINK_STRING, 0), + VARLINK_FIELD_COMMENT("The priority of the log message, using the BSD syslog priority levels"), + VARLINK_DEFINE_FIELD(priority, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + ListTransfers, + VARLINK_FIELD_COMMENT("Image class to filter by"), + VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("A unique numeric identifier for the ongoing transfer"), + VARLINK_DEFINE_OUTPUT(id, VARLINK_INT, 0), + VARLINK_FIELD_COMMENT("The type of transfer"), + VARLINK_DEFINE_OUTPUT_BY_TYPE(type, TransferType, 0), + VARLINK_FIELD_COMMENT("The remote URL"), + VARLINK_DEFINE_OUTPUT(remote, VARLINK_STRING, 0), + VARLINK_FIELD_COMMENT("The local image name"), + VARLINK_DEFINE_OUTPUT(local, VARLINK_STRING, 0), + VARLINK_FIELD_COMMENT("The class of the image"), + VARLINK_DEFINE_OUTPUT_BY_TYPE(class, ImageClass, 0), + VARLINK_FIELD_COMMENT("Progress in percent"), + VARLINK_DEFINE_OUTPUT(percent, VARLINK_FLOAT, 0)); + +static VARLINK_DEFINE_METHOD( + Pull, + VARLINK_FIELD_COMMENT("The remote URL to download from"), + VARLINK_DEFINE_INPUT(remote, VARLINK_STRING, 0), + VARLINK_FIELD_COMMENT("The local image name to download to"), + VARLINK_DEFINE_INPUT(local, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("The type of the resource"), + VARLINK_DEFINE_INPUT_BY_TYPE(type, RemoteType, 0), + VARLINK_FIELD_COMMENT("The image class"), + VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, 0), + VARLINK_FIELD_COMMENT("The whether and how thoroughly to verify the download before installing it locally. Defauts to 'signature'."), + VARLINK_DEFINE_INPUT_BY_TYPE(verify, ImageVerify, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("If true, an existing image by the local name is deleted. Defaults to false."), + VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Whether to make the image read-only after downloading. Defaults ot false."), + VARLINK_DEFINE_INPUT(readOnly, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Whether to keep a pristine copy of the download separate from the locally installed image. Defaults to false."), + VARLINK_DEFINE_INPUT(keepDownload, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Whether to permit interactive authentication. Defaults to false."), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("A progress update, as percent value"), + VARLINK_DEFINE_OUTPUT(progress, VARLINK_FLOAT, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("A log message about the ongoing transfer"), + VARLINK_DEFINE_OUTPUT_BY_TYPE(log, LogMessage, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("The numeric ID of this download"), + VARLINK_DEFINE_OUTPUT(id, VARLINK_INT, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_ERROR(AlreadyInProgress); +static VARLINK_DEFINE_ERROR(TransferCancelled); +static VARLINK_DEFINE_ERROR(TransferFailed); +static VARLINK_DEFINE_ERROR(NoTransfers); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Import, + "io.systemd.Import", + VARLINK_SYMBOL_COMMENT("Describes the class of images"), + &vl_type_ImageClass, + VARLINK_SYMBOL_COMMENT("Describes the type of a images to transfer"), + &vl_type_RemoteType, + VARLINK_SYMBOL_COMMENT("Describes the type of a transfer"), + &vl_type_TransferType, + VARLINK_SYMBOL_COMMENT("Describes whether and how thoroughly to verify the download before installing it locally"), + &vl_type_ImageVerify, + VARLINK_SYMBOL_COMMENT("Structure for log messages associated with a transfer operation"), + &vl_type_LogMessage, + VARLINK_SYMBOL_COMMENT("List ongoing transfers, or query details about specific transfers"), + &vl_method_ListTransfers, + VARLINK_SYMBOL_COMMENT("Download a .tar or .raw file. This must be called with the 'more' flag enabled. It will immediately return the numeric ID of the transfer, and then follow up with progress and log message updates, until the transfer is complete."), + &vl_method_Pull, + VARLINK_SYMBOL_COMMENT("A transfer for the specified file is already ongoing"), + &vl_error_AlreadyInProgress, + VARLINK_SYMBOL_COMMENT("The transfer has been cancelled on user request"), + &vl_error_TransferCancelled, + VARLINK_SYMBOL_COMMENT("The transfer failed"), + &vl_error_TransferFailed, + VARLINK_SYMBOL_COMMENT("No currently ongoing transfer"), + &vl_error_NoTransfers); diff --git a/src/shared/varlink-io.systemd.Import.h b/src/shared/varlink-io.systemd.Import.h new file mode 100644 index 0000000000..6579c29b39 --- /dev/null +++ b/src/shared/varlink-io.systemd.Import.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Import; diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 2d25a345d7..4d93527273 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -14,12 +14,55 @@ static VARLINK_DEFINE_METHOD( VARLINK_DEFINE_INPUT(ifIndices, VARLINK_INT, VARLINK_ARRAY|VARLINK_NULLABLE), VARLINK_DEFINE_INPUT(vSockCid, VARLINK_INT, VARLINK_NULLABLE), VARLINK_DEFINE_INPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE)); + VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Controls whether to allocate a scope unit for the machine to register. If false, the client already took care of that and registered a service/scope specific to the machine."), + VARLINK_DEFINE_INPUT(allocateUnit, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Whether to allow interactive authentication on this operation."), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE)); +static VARLINK_DEFINE_STRUCT_TYPE( + Timestamp, + VARLINK_FIELD_COMMENT("Timestamp in µs in the CLOCK_REALTIME clock (wallclock)"), + VARLINK_DEFINE_FIELD(realtime, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Timestamp in µs in the CLOCK_MONOTONIC clock"), + VARLINK_DEFINE_FIELD(monotonic, VARLINK_INT, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + List, + VARLINK_FIELD_COMMENT("If non-null the name of a running machine to report details on. If null/unspecified enumerates all running machines."), + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Name of the machine"), + VARLINK_DEFINE_OUTPUT(name, VARLINK_STRING, 0), + VARLINK_FIELD_COMMENT("128bit ID identifying this machine, formatted in hexadecimal"), + VARLINK_DEFINE_OUTPUT(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Name of the software that registered this machine"), + VARLINK_DEFINE_OUTPUT(service, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("The class of this machine"), + VARLINK_DEFINE_OUTPUT(class, VARLINK_STRING, 0), + VARLINK_FIELD_COMMENT("Leader process PID of this machine"), + VARLINK_DEFINE_OUTPUT(leader, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Root directory of this machine, if known, relative to host file system"), + VARLINK_DEFINE_OUTPUT(rootDirectory, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("The service manager unit this machine resides in"), + VARLINK_DEFINE_OUTPUT(unit, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Timestamp when the machine was activated"), + VARLINK_DEFINE_OUTPUT_BY_TYPE(timestamp, Timestamp, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("AF_VSOCK CID of the machine if known and applicable"), + VARLINK_DEFINE_OUTPUT(vSockCid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("SSH address to connect to"), + VARLINK_DEFINE_OUTPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_ERROR(NoSuchMachine); static VARLINK_DEFINE_ERROR(MachineExists); VARLINK_DEFINE_INTERFACE( io_systemd_Machine, "io.systemd.Machine", + VARLINK_SYMBOL_COMMENT("A timestamp object consisting of both CLOCK_REALTIME and CLOCK_MONOTONIC timestamps"), + &vl_type_Timestamp, &vl_method_Register, + VARLINK_SYMBOL_COMMENT("List running machines"), + &vl_method_List, + VARLINK_SYMBOL_COMMENT("No matching machine currently running"), + &vl_error_NoSuchMachine, &vl_error_MachineExists); diff --git a/src/shared/varlink-io.systemd.service.c b/src/shared/varlink-io.systemd.service.c index 7c22c33ea8..87479e3758 100644 --- a/src/shared/varlink-io.systemd.service.c +++ b/src/shared/varlink-io.systemd.service.c @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include <unistd.h> diff --git a/src/shared/varlink-io.systemd.service.h b/src/shared/varlink-io.systemd.service.h index 286140b75a..6ea1a0f11b 100644 --- a/src/shared/varlink-io.systemd.service.h +++ b/src/shared/varlink-io.systemd.service.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "varlink.h" diff --git a/src/shared/varlink.c b/src/shared/varlink.c index 8329f39021..35879e3872 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "errno-util.h" +#include "escape.h" #include "fd-util.h" #include "glyph-util.h" #include "hashmap.h" @@ -139,7 +140,8 @@ struct Varlink { * at most. */ unsigned n_pending; - int fd; + int input_fd; + int output_fd; char *input_buffer; /* valid data starts at input_buffer_index, ends at input_buffer_index+input_buffer_size */ size_t input_buffer_index; @@ -185,7 +187,8 @@ struct Varlink { bool write_disconnected:1; bool read_disconnected:1; - bool prefer_read_write:1; + bool prefer_read:1; + bool prefer_write:1; bool got_pollhup:1; bool allow_fd_passing_input:1; @@ -203,7 +206,8 @@ struct Varlink { char *description; sd_event *event; - sd_event_source *io_event_source; + sd_event_source *input_event_source; + sd_event_source *output_event_source; sd_event_source *time_event_source; sd_event_source *quit_event_source; sd_event_source *defer_event_source; @@ -357,7 +361,8 @@ static int varlink_new(Varlink **ret) { *v = (Varlink) { .n_ref = 1, - .fd = -EBADF, + .input_fd = -EBADF, + .output_fd = -EBADF, .state = _VARLINK_STATE_INVALID, @@ -387,11 +392,11 @@ int varlink_connect_address(Varlink **ret, const char *address) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (v->fd < 0) + v->input_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (v->input_fd < 0) return log_debug_errno(errno, "Failed to create AF_UNIX socket: %m"); - v->fd = fd_move_above_stdio(v->fd); + v->output_fd = v->input_fd = fd_move_above_stdio(v->input_fd); v->af = AF_UNIX; r = sockaddr_un_set_path(&sockaddr.un, address); @@ -402,9 +407,9 @@ int varlink_connect_address(Varlink **ret, const char *address) { /* This is a file system path, and too long to fit into sockaddr_un. Let's connect via O_PATH * to this socket. */ - r = connect_unix_path(v->fd, AT_FDCWD, address); + r = connect_unix_path(v->input_fd, AT_FDCWD, address); } else - r = RET_NERRNO(connect(v->fd, &sockaddr.sa, r)); + r = RET_NERRNO(connect(v->input_fd, &sockaddr.sa, r)); if (r < 0) { if (!IN_SET(r, -EAGAIN, -EINPROGRESS)) @@ -507,7 +512,7 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->fd = TAKE_FD(pair[0]); + v->output_fd = v->input_fd = TAKE_FD(pair[0]); v->af = AF_UNIX; v->exec_pid = TAKE_PID(pid); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -516,7 +521,18 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) { return 0; } -static int varlink_connect_ssh(Varlink **ret, const char *where) { +static int ssh_path(const char **ret) { + assert(ret); + + const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; + if (!path_is_valid(ssh)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH path is not valid, refusing: %s", ssh); + + *ret = ssh; + return 0; +} + +static int varlink_connect_ssh_unix(Varlink **ret, const char *where) { _cleanup_close_pair_ int pair[2] = EBADF_PAIR; _cleanup_(sigkill_waitp) pid_t pid = 0; int r; @@ -527,9 +543,10 @@ static int varlink_connect_ssh(Varlink **ret, const char *where) { /* Connects to an SSH server via OpenSSH 9.4's -W switch to connect to a remote AF_UNIX socket. For * now we do not expose this function directly, but only via varlink_connect_url(). */ - const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; - if (!path_is_valid(ssh)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH path is not valid, refusing: %s", ssh); + const char *ssh; + r = ssh_path(&ssh); + if (r < 0) + return r; const char *e = strchr(where, ':'); if (!e) @@ -583,7 +600,7 @@ static int varlink_connect_ssh(Varlink **ret, const char *where) { if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->fd = TAKE_FD(pair[0]); + v->output_fd = v->input_fd = TAKE_FD(pair[0]); v->af = AF_UNIX; v->exec_pid = TAKE_PID(pid); varlink_set_state(v, VARLINK_IDLE_CLIENT); @@ -592,13 +609,107 @@ static int varlink_connect_ssh(Varlink **ret, const char *where) { return 0; } +static int varlink_connect_ssh_exec(Varlink **ret, const char *where) { + _cleanup_close_pair_ int input_pipe[2] = EBADF_PAIR, output_pipe[2] = EBADF_PAIR; + _cleanup_(sigkill_waitp) pid_t pid = 0; + int r; + + assert_return(ret, -EINVAL); + assert_return(where, -EINVAL); + + /* Connects to an SSH server to connect to a remote process' stdin/stdout. For now we do not expose + * this function directly, but only via varlink_connect_url(). */ + + const char *ssh; + r = ssh_path(&ssh); + if (r < 0) + return r; + + const char *e = strchr(where, ':'); + if (!e) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH specification lacks a : separator between host and path, refusing: %s", where); + + _cleanup_free_ char *h = strndup(where, e - where); + if (!h) + return log_oom_debug(); + + _cleanup_strv_free_ char **cmdline = NULL; + r = strv_split_full(&cmdline, e + 1, /* separators= */ NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r < 0) + return log_debug_errno(r, "Failed to split command line: %m"); + if (strv_isempty(cmdline)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Remote command line is empty, refusing."); + + _cleanup_strv_free_ char **full_cmdline = NULL; + full_cmdline = strv_new("ssh", "-e", "none", "-T", h, "env", "SYSTEMD_VARLINK_LISTEN=-"); + if (!full_cmdline) + return log_oom_debug(); + r = strv_extend_strv(&full_cmdline, cmdline, /* filter_duplicates= */ false); + if (r < 0) + return log_oom_debug(); + + _cleanup_free_ char *j = NULL; + j = quote_command_line(full_cmdline, SHELL_ESCAPE_EMPTY); + if (!j) + return log_oom_debug(); + + log_debug("Forking off SSH child process: %s", j); + + if (pipe2(input_pipe, O_CLOEXEC) < 0) + return log_debug_errno(errno, "Failed to allocate input pipe: %m"); + if (pipe2(output_pipe, O_CLOEXEC) < 0) + return log_debug_errno(errno, "Failed to allocate output pipe: %m"); + + r = safe_fork_full( + "(sd-vlssh)", + /* stdio_fds= */ (int[]) { input_pipe[0], output_pipe[1], STDERR_FILENO }, + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO, + &pid); + if (r < 0) + return log_debug_errno(r, "Failed to spawn process: %m"); + if (r == 0) { + /* Child */ + execvp(ssh, full_cmdline); + log_debug_errno(errno, "Failed to invoke %s: %m", j); + _exit(EXIT_FAILURE); + } + + input_pipe[0] = safe_close(input_pipe[0]); + output_pipe[1] = safe_close(output_pipe[1]); + + r = fd_nonblock(input_pipe[1], true); + if (r < 0) + return log_debug_errno(r, "Failed to make input pipe non-blocking: %m"); + + r = fd_nonblock(output_pipe[0], true); + if (r < 0) + return log_debug_errno(r, "Failed to make output pipe non-blocking: %m"); + + Varlink *v; + r = varlink_new(&v); + if (r < 0) + return log_debug_errno(r, "Failed to create varlink object: %m"); + + v->input_fd = TAKE_FD(output_pipe[0]); + v->output_fd = TAKE_FD(input_pipe[1]); + v->af = AF_UNSPEC; + v->exec_pid = TAKE_PID(pid); + varlink_set_state(v, VARLINK_IDLE_CLIENT); + + *ret = v; + return 0; +} + int varlink_connect_url(Varlink **ret, const char *url) { _cleanup_free_ char *c = NULL; const char *p; enum { SCHEME_UNIX, SCHEME_EXEC, - SCHEME_SSH, + SCHEME_SSH_UNIX, + SCHEME_SSH_EXEC, } scheme; int r; @@ -616,8 +727,10 @@ int varlink_connect_url(Varlink **ret, const char *url) { scheme = SCHEME_UNIX; else if ((p = startswith(url, "exec:"))) scheme = SCHEME_EXEC; - else if ((p = startswith(url, "ssh:"))) - scheme = SCHEME_SSH; + else if ((p = STARTSWITH_SET(url, "ssh:", "ssh-unix:"))) + scheme = SCHEME_SSH_UNIX; + else if ((p = startswith(url, "ssh-exec:"))) + scheme = SCHEME_SSH_EXEC; else return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported."); @@ -626,8 +739,10 @@ int varlink_connect_url(Varlink **ret, const char *url) { if (p[strcspn(p, ";?#")] != '\0') return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported."); - if (scheme == SCHEME_SSH) - return varlink_connect_ssh(ret, p); + if (scheme == SCHEME_SSH_UNIX) + return varlink_connect_ssh_unix(ret, p); + if (scheme == SCHEME_SSH_EXEC) + return varlink_connect_ssh_exec(ret, p); if (scheme == SCHEME_EXEC || p[0] != '@') { /* no path validity checks for abstract namespace sockets */ @@ -648,23 +763,37 @@ int varlink_connect_url(Varlink **ret, const char *url) { return varlink_connect_address(ret, c ?: p); } -int varlink_connect_fd(Varlink **ret, int fd) { +int varlink_connect_fd_pair(Varlink **ret, int input_fd, int output_fd, const struct ucred *override_ucred) { Varlink *v; int r; assert_return(ret, -EINVAL); - assert_return(fd >= 0, -EBADF); + assert_return(input_fd >= 0, -EBADF); + assert_return(output_fd >= 0, -EBADF); - r = fd_nonblock(fd, true); + r = fd_nonblock(input_fd, true); if (r < 0) - return log_debug_errno(r, "Failed to make fd %d nonblocking: %m", fd); + return log_debug_errno(r, "Failed to make input fd %d nonblocking: %m", input_fd); + + if (input_fd != output_fd) { + r = fd_nonblock(output_fd, true); + if (r < 0) + return log_debug_errno(r, "Failed to make output fd %d nonblocking: %m", output_fd); + } r = varlink_new(&v); if (r < 0) return log_debug_errno(r, "Failed to create varlink object: %m"); - v->fd = fd; - v->af = -1, + v->input_fd = input_fd; + v->output_fd = output_fd; + v->af = -1; + + if (override_ucred) { + v->ucred = *override_ucred; + v->ucred_acquired = true; + } + varlink_set_state(v, VARLINK_IDLE_CLIENT); /* Note that if this function is called we assume the passed socket (if it is one) is already @@ -678,10 +807,15 @@ int varlink_connect_fd(Varlink **ret, int fd) { return 0; } +int varlink_connect_fd(Varlink **ret, int fd) { + return varlink_connect_fd_pair(ret, fd, fd, /* override_ucred= */ NULL); +} + static void varlink_detach_event_sources(Varlink *v) { assert(v); - v->io_event_source = sd_event_source_disable_unref(v->io_event_source); + v->input_event_source = sd_event_source_disable_unref(v->input_event_source); + v->output_event_source = sd_event_source_disable_unref(v->output_event_source); v->time_event_source = sd_event_source_disable_unref(v->time_event_source); v->quit_event_source = sd_event_source_disable_unref(v->quit_event_source); v->defer_event_source = sd_event_source_disable_unref(v->defer_event_source); @@ -706,7 +840,11 @@ static void varlink_clear(Varlink *v) { varlink_detach_event_sources(v); - v->fd = safe_close(v->fd); + if (v->input_fd != v->output_fd) { + v->input_fd = safe_close(v->input_fd); + v->output_fd = safe_close(v->output_fd); + } else + v->output_fd = v->input_fd = safe_close(v->input_fd); varlink_clear_current(v); @@ -821,7 +959,7 @@ static int varlink_write(Varlink *v) { if (v->output_buffer_size == 0) return 0; - assert(v->fd >= 0); + assert(v->output_fd >= 0); if (v->n_output_fds > 0) { /* If we shall send fds along, we must use sendmsg() */ struct iovec iov = { @@ -842,20 +980,20 @@ static int varlink_write(Varlink *v) { control->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(control), v->output_fds, sizeof(int) * v->n_output_fds); - n = sendmsg(v->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + n = sendmsg(v->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); } else { /* We generally prefer recv()/send() (mostly because of MSG_NOSIGNAL) but also want to be compatible * with non-socket IO, hence fall back automatically. * * Use a local variable to help gcc figure out that we set 'n' in all cases. */ - bool prefer_write = v->prefer_read_write; + bool prefer_write = v->prefer_write; if (!prefer_write) { - n = send(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL); + n = send(v->output_fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size, MSG_DONTWAIT|MSG_NOSIGNAL); if (n < 0 && errno == ENOTSOCK) - prefer_write = v->prefer_read_write = true; + prefer_write = v->prefer_write = true; } if (prefer_write) - n = write(v->fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size); + n = write(v->output_fd, v->output_buffer + v->output_buffer_index, v->output_buffer_size); } if (n < 0) { if (errno == EAGAIN) @@ -914,7 +1052,7 @@ static int varlink_read(Varlink *v) { if (v->input_buffer_size >= VARLINK_BUFFER_MAX) return -ENOBUFS; - assert(v->fd >= 0); + assert(v->input_fd >= 0); if (MALLOC_SIZEOF_SAFE(v->input_buffer) <= v->input_buffer_index + v->input_buffer_size) { size_t add; @@ -961,16 +1099,16 @@ static int varlink_read(Varlink *v) { .msg_controllen = v->input_control_buffer_size, }; - n = recvmsg_safe(v->fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + n = recvmsg_safe(v->input_fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); } else { - bool prefer_read = v->prefer_read_write; + bool prefer_read = v->prefer_read; if (!prefer_read) { - n = recv(v->fd, p, rs, MSG_DONTWAIT); + n = recv(v->input_fd, p, rs, MSG_DONTWAIT); if (n < 0 && errno == ENOTSOCK) - prefer_read = v->prefer_read_write = true; + prefer_read = v->prefer_read = true; } if (prefer_read) - n = read(v->fd, p, rs); + n = read(v->input_fd, p, rs); } if (n < 0) { if (errno == EAGAIN) @@ -1666,7 +1804,7 @@ static void handle_revents(Varlink *v, int revents) { } int varlink_wait(Varlink *v, usec_t timeout) { - int r, fd, events; + int r, events; usec_t t; assert_return(v, -EINVAL); @@ -1691,22 +1829,42 @@ int varlink_wait(Varlink *v, usec_t timeout) { (t == USEC_INFINITY || timeout < t)) t = timeout; - fd = varlink_get_fd(v); - if (fd < 0) - return fd; - events = varlink_get_events(v); if (events < 0) return events; - r = fd_wait_for_event(fd, events, t); + struct pollfd pollfd[2]; + size_t n_poll_fd = 0; + + if (v->input_fd == v->output_fd) { + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = v->input_fd, + .events = events, + }; + } else { + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = v->input_fd, + .events = events & POLLIN, + }; + pollfd[n_poll_fd++] = (struct pollfd) { + .fd = v->output_fd, + .events = events & POLLOUT, + }; + }; + + r = ppoll_usec(pollfd, n_poll_fd, t); if (ERRNO_IS_NEG_TRANSIENT(r)) /* Treat EINTR as not a timeout, but also nothing happened, and * the caller gets a chance to call back into us */ return 1; if (r <= 0) return r; - handle_revents(v, r); + /* Merge the seen events into one */ + int revents = 0; + FOREACH_ARRAY(p, pollfd, n_poll_fd) + revents |= p->revents; + + handle_revents(v, revents); return 1; } @@ -1725,10 +1883,12 @@ int varlink_get_fd(Varlink *v) { if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (v->fd < 0) + if (v->input_fd != v->output_fd) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "Separate file descriptors for input/output set."); + if (v->input_fd < 0) return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid fd."); - return v->fd; + return v->input_fd; } int varlink_get_events(Varlink *v) { @@ -1797,7 +1957,7 @@ int varlink_flush(Varlink *v) { continue; } - r = fd_wait_for_event(v->fd, POLLOUT, USEC_INFINITY); + r = fd_wait_for_event(v->output_fd, POLLOUT, USEC_INFINITY); if (ERRNO_IS_NEG_TRANSIENT(r)) continue; if (r < 0) @@ -2794,7 +2954,12 @@ static int varlink_acquire_ucred(Varlink *v) { if (v->ucred_acquired) return 0; - r = getpeercred(v->fd, &v->ucred); + /* If we are connected asymmetrically, let's refuse, since it's not clear if caller wants to know + * peer on read or write fd */ + if (v->input_fd != v->output_fd) + return -EBADF; + + r = getpeercred(v->input_fd, &v->ucred); if (r < 0) return r; @@ -2859,7 +3024,10 @@ static int varlink_acquire_pidfd(Varlink *v) { if (v->peer_pidfd >= 0) return 0; - v->peer_pidfd = getpeerpidfd(v->fd); + if (v->input_fd != v->output_fd) + return -EBADF; + + v->peer_pidfd = getpeerpidfd(v->input_fd); if (v->peer_pidfd < 0) return v->peer_pidfd; @@ -2962,7 +3130,14 @@ static int prepare_callback(sd_event_source *s, void *userdata) { if (e < 0) return e; - r = sd_event_source_set_io_events(v->io_event_source, e); + if (v->input_event_source == v->output_event_source) + /* Same fd for input + output */ + r = sd_event_source_set_io_events(v->input_event_source, e); + else { + r = sd_event_source_set_io_events(v->input_event_source, e & EPOLLIN); + if (r >= 0) + r = sd_event_source_set_io_events(v->output_event_source, e & EPOLLOUT); + } if (r < 0) return varlink_log_errno(v, r, "Failed to set source events: %m"); @@ -3029,19 +3204,33 @@ int varlink_attach_event(Varlink *v, sd_event *e, int64_t priority) { (void) sd_event_source_set_description(v->quit_event_source, "varlink-quit"); - r = sd_event_add_io(v->event, &v->io_event_source, v->fd, 0, io_callback, v); + r = sd_event_add_io(v->event, &v->input_event_source, v->input_fd, 0, io_callback, v); if (r < 0) goto fail; - r = sd_event_source_set_prepare(v->io_event_source, prepare_callback); + r = sd_event_source_set_prepare(v->input_event_source, prepare_callback); if (r < 0) goto fail; - r = sd_event_source_set_priority(v->io_event_source, priority); + r = sd_event_source_set_priority(v->input_event_source, priority); if (r < 0) goto fail; - (void) sd_event_source_set_description(v->io_event_source, "varlink-io"); + (void) sd_event_source_set_description(v->input_event_source, "varlink-input"); + + if (v->input_fd == v->output_fd) + v->output_event_source = sd_event_source_ref(v->input_event_source); + else { + r = sd_event_add_io(v->event, &v->output_event_source, v->output_fd, 0, io_callback, v); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(v->output_event_source, priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(v->output_event_source, "varlink-output"); + } r = sd_event_add_defer(v->event, &v->defer_event_source, defer_callback, v); if (r < 0) @@ -3187,16 +3376,23 @@ static int verify_unix_socket(Varlink *v) { * • otherwise: v->af contains the address family we determined */ if (v->af < 0) { + /* If we have distinct input + output fds, we don't consider ourselves to be connected via a regular + * AF_UNIX socket. */ + if (v->input_fd != v->output_fd) { + v->af = AF_UNSPEC; + return -ENOTSOCK; + } + struct stat st; - if (fstat(v->fd, &st) < 0) + if (fstat(v->input_fd, &st) < 0) return -errno; if (!S_ISSOCK(st.st_mode)) { v->af = AF_UNSPEC; return -ENOTSOCK; } - v->af = socket_get_family(v->fd); + v->af = socket_get_family(v->input_fd); if (v->af < 0) return v->af; } @@ -3379,19 +3575,34 @@ static int count_connection(VarlinkServer *server, const struct ucred *ucred) { return 0; } -int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) { +int varlink_server_add_connection_pair( + VarlinkServer *server, + int input_fd, + int output_fd, + const struct ucred *override_ucred, + Varlink **ret) { + _cleanup_(varlink_unrefp) Varlink *v = NULL; struct ucred ucred = UCRED_INVALID; bool ucred_acquired; int r; assert_return(server, -EINVAL); - assert_return(fd >= 0, -EBADF); + assert_return(input_fd >= 0, -EBADF); + assert_return(output_fd >= 0, -EBADF); if ((server->flags & (VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_ACCOUNT_UID)) != 0) { - r = getpeercred(fd, &ucred); - if (r < 0) - return varlink_server_log_errno(server, r, "Failed to acquire peer credentials of incoming socket, refusing: %m"); + + if (override_ucred) + ucred = *override_ucred; + else { + if (input_fd != output_fd) + return varlink_server_log_errno(server, SYNTHETIC_ERRNO(EOPNOTSUPP), "Cannot determine peer identity of connection with separate input/output, refusing: %m"); + + r = getpeercred(input_fd, &ucred); + if (r < 0) + return varlink_server_log_errno(server, r, "Failed to acquire peer credentials of incoming socket, refusing: %m"); + } ucred_acquired = true; @@ -3411,7 +3622,8 @@ int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) if (r < 0) return r; - v->fd = fd; + v->input_fd = input_fd; + v->output_fd = output_fd; if (server->flags & VARLINK_SERVER_INHERIT_USERDATA) v->userdata = server->userdata; @@ -3421,7 +3633,7 @@ int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) } _cleanup_free_ char *desc = NULL; - if (asprintf(&desc, "%s-%i", varlink_server_description(server), v->fd) >= 0) + if (asprintf(&desc, "%s-%i-%i", varlink_server_description(server), input_fd, output_fd) >= 0) v->description = TAKE_PTR(desc); /* Link up the server and the connection, and take reference in both directions. Note that the @@ -3436,7 +3648,8 @@ int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) r = varlink_attach_event(v, server->event, server->event_priority); if (r < 0) { varlink_log_errno(v, r, "Failed to attach new connection: %m"); - v->fd = -EBADF; /* take the fd out of the connection again */ + TAKE_FD(v->input_fd); /* take the fd out of the connection again */ + TAKE_FD(v->output_fd); varlink_close(v); return r; } @@ -3448,6 +3661,10 @@ int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) return 0; } +int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) { + return varlink_server_add_connection_pair(server, fd, fd, /* override_ucred= */ NULL, ret); +} + static VarlinkServerSocket *varlink_server_socket_free(VarlinkServerSocket *ss) { if (!ss) return NULL; @@ -3597,6 +3814,66 @@ int varlink_server_listen_address(VarlinkServer *s, const char *address, mode_t return 0; } +int varlink_server_add_connection_stdio(VarlinkServer *s, Varlink **ret) { + _cleanup_close_ int input_fd = -EBADF, output_fd = -EBADF; + int r; + + assert_return(s, -EINVAL); + + input_fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 3); + if (input_fd < 0) + return -errno; + + output_fd = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3); + if (output_fd < 0) + return -errno; + + r = rearrange_stdio(-EBADF, -EBADF, STDERR_FILENO); + if (r < 0) + return r; + + r = fd_nonblock(input_fd, true); + if (r < 0) + return r; + + r = fd_nonblock(output_fd, true); + if (r < 0) + return r; + + struct stat input_st; + if (fstat(input_fd, &input_st) < 0) + return -errno; + + struct stat output_st; + if (fstat(output_fd, &output_st) < 0) + return -errno; + + /* If stdin/stdout are both pipes and have the same owning uid/gid then let's synthesize a "struct + * ucred" from the owning UID/GID, since we got them passed in with such ownership. We'll not fill in + * the PID however, since there's no way to know which process created a pipe. */ + struct ucred ucred, *pucred; + if (S_ISFIFO(input_st.st_mode) && + S_ISFIFO(output_st.st_mode) && + input_st.st_uid == output_st.st_uid && + input_st.st_gid == output_st.st_gid) { + ucred = (struct ucred) { + .uid = input_st.st_uid, + .gid = input_st.st_gid, + }; + pucred = &ucred; + } else + pucred = NULL; + + r = varlink_server_add_connection_pair(s, input_fd, output_fd, pucred, ret); + if (r < 0) + return r; + + TAKE_FD(input_fd); + TAKE_FD(output_fd); + + return 0; +} + int varlink_server_listen_auto(VarlinkServer *s) { _cleanup_strv_free_ char **names = NULL; int r, n = 0; @@ -3637,12 +3914,17 @@ int varlink_server_listen_auto(VarlinkServer *s) { n++; } - /* For debug purposes let's listen on an explicitly specified address */ + /* Let's listen on an explicitly specified address */ const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); if (e) { - r = varlink_server_listen_address(s, e, FLAGS_SET(s->flags, VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666); + if (streq(e, "-")) + r = varlink_server_add_connection_stdio(s, /* ret= */ NULL); + else + r = varlink_server_listen_address(s, e, FLAGS_SET(s->flags, VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666); if (r < 0) return r; + + n++; } return n; @@ -4092,7 +4374,7 @@ int varlink_invocation(VarlinkInvocationFlags flags) { /* Returns true if this is a "pure" varlink server invocation, i.e. with one fd passed. */ - const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); /* Permit a manual override for testing purposes */ + const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); /* Permit an explicit override */ if (e) return true; @@ -4162,3 +4444,51 @@ int varlink_error_to_errno(const char *error, sd_json_variant *parameters) { return -EBADR; /* Catch-all */ } + +int varlink_many_notifyb(Set *s, ...) { + int r; + + /* Equivalent to varlink_notifyb(), but does this for each entry of the supplied set of Varlink connections */ + + if (set_isempty(s)) + return 0; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL; + va_list ap; + va_start(ap, s); + r = sd_json_buildv(¶meters, ap); + va_end(ap); + if (r < 0) + return r; + + int ret = 1; + Varlink *link; + SET_FOREACH(link, s) + RET_GATHER(ret, varlink_notify(link, parameters)); + + return ret; +} + +int varlink_many_reply(Set *s, sd_json_variant *parameters) { + if (set_isempty(s)) + return 0; + + int ret = 1; + Varlink *link; + SET_FOREACH(link, s) + RET_GATHER(ret, varlink_reply(link, parameters)); + + return ret; +} + +int varlink_many_error(Set *s, const char *error_id, sd_json_variant *parameters) { + if (set_isempty(s)) + return 0; + + int ret = 1; + Varlink *link; + SET_FOREACH(link, s) + RET_GATHER(ret, varlink_error(link, error_id, parameters)); + + return ret; +} diff --git a/src/shared/varlink.h b/src/shared/varlink.h index 739786b975..47eed171de 100644 --- a/src/shared/varlink.h +++ b/src/shared/varlink.h @@ -1,10 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "sd-event.h" +#include <sys/socket.h> +#include "sd-event.h" #include "sd-json.h" + #include "pidref.h" +#include "set.h" #include "time-util.h" #include "varlink-idl.h" @@ -60,6 +63,7 @@ int varlink_connect_address(Varlink **ret, const char *address); int varlink_connect_exec(Varlink **ret, const char *command, char **argv); int varlink_connect_url(Varlink **ret, const char *url); int varlink_connect_fd(Varlink **ret, int fd); +int varlink_connect_fd_pair(Varlink **ret, int input_fd, int output_fd, const struct ucred *override_ucred); Varlink* varlink_ref(Varlink *link); Varlink* varlink_unref(Varlink *v); @@ -217,6 +221,8 @@ int varlink_server_listen_address(VarlinkServer *s, const char *address, mode_t int varlink_server_listen_fd(VarlinkServer *s, int fd); int varlink_server_listen_auto(VarlinkServer *s); int varlink_server_add_connection(VarlinkServer *s, int fd, Varlink **ret); +int varlink_server_add_connection_pair(VarlinkServer *s, int input_fd, int output_fd, const struct ucred *ucred_override, Varlink **ret); +int varlink_server_add_connection_stdio(VarlinkServer *s, Varlink **ret); /* Bind callbacks */ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMethod callback); @@ -264,6 +270,12 @@ int varlink_invocation(VarlinkInvocationFlags flags); int varlink_error_to_errno(const char *error, sd_json_variant *parameters); +int varlink_many_notifyb(Set *s, ...); +#define varlink_many_notifybo(s, ...) \ + varlink_many_notifyb((s), SD_JSON_BUILD_OBJECT(__VA_ARGS__)) +int varlink_many_reply(Set *s, sd_json_variant *parameters); +int varlink_many_error(Set *s, const char *error_id, sd_json_variant *parameters); + DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref); diff --git a/src/ssh-generator/20-systemd-ssh-proxy.conf.in b/src/ssh-generator/20-systemd-ssh-proxy.conf.in index b97e0f5340..912cc1f9ac 100644 --- a/src/ssh-generator/20-systemd-ssh-proxy.conf.in +++ b/src/ssh-generator/20-systemd-ssh-proxy.conf.in @@ -1,8 +1,15 @@ # SPDX-License-Identifier: LGPL-2.1-or-later # -# Make sure unix/* and vsock/* can be used to connect to AF_UNIX and AF_VSOCK paths +# Allow connecting to the local host directly via ".host" +Host .host machine/.host + ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p + ProxyUseFdpass yes + CheckHostIP no + +# Make sure unix/* and vsock/* can be used to connect to AF_UNIX and AF_VSOCK paths. +# Make sure machine/* can be used to connect to local machines registered in machined. # -Host unix/* vsock/* +Host unix/* vsock/* machine/* ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy %h %p ProxyUseFdpass yes CheckHostIP no @@ -10,9 +17,3 @@ Host unix/* vsock/* # Disable all kinds of host identity checks, since these addresses are generally ephemeral. StrictHostKeyChecking no UserKnownHostsFile /dev/null - -# Allow connecting to the local host directly via ".host" -Host .host - ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p - ProxyUseFdpass yes - CheckHostIP no diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c index 7021ddd8a6..3631113612 100644 --- a/src/ssh-generator/ssh-proxy.c +++ b/src/ssh-generator/ssh-proxy.c @@ -14,21 +14,19 @@ #include "socket-util.h" #include "string-util.h" #include "strv.h" +#include "varlink.h" -static int process_vsock(const char *host, const char *port) { +static int process_vsock_cid(unsigned cid, const char *port) { int r; - assert(host); + assert(cid != VMADDR_CID_ANY); assert(port); union sockaddr_union sa = { + .vm.svm_cid = cid, .vm.svm_family = AF_VSOCK, }; - r = vsock_parse_cid(host, &sa.vm.svm_cid); - if (r < 0) - return log_error_errno(r, "Failed to parse vsock cid: %s", host); - r = vsock_parse_port(port, &sa.vm.svm_port); if (r < 0) return log_error_errno(r, "Failed to parse vsock port: %s", port); @@ -47,6 +45,21 @@ static int process_vsock(const char *host, const char *port) { log_debug("Successfully sent AF_VSOCK socket via STDOUT."); return 0; + +} + +static int process_vsock_string(const char *host, const char *port) { + unsigned cid; + int r; + + assert(host); + assert(port); + + r = vsock_parse_cid(host, &cid); + if (r < 0) + return log_error_errno(r, "Failed to parse vsock cid: %s", host); + + return process_vsock_cid(cid, port); } static int process_unix(const char *path) { @@ -124,6 +137,43 @@ static int process_vsock_mux(const char *path, const char *port) { return 0; } +static int process_machine(const char *machine, const char *port) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r; + + assert(machine); + assert(port); + + r = varlink_connect_address(&vl, "/run/systemd/machine/io.systemd.Machine"); + if (r < 0) + return log_error_errno(r, "Failed to connect to machined on /run/systemd/machine/io.systemd.Machine: %m"); + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; + r = varlink_callbo_and_log( + vl, + "io.systemd.Machine.List", + &result, + SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(machine))); + if (r < 0) + return r; + + uint32_t cid = VMADDR_CID_ANY; + + const sd_json_dispatch_field dispatch_table[] = { + { "vSockCid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, PTR_TO_SIZE(&cid), 0 }, + {} + }; + + r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse Varlink reply: %m"); + + if (cid == VMADDR_CID_ANY) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine has no AF_VSOCK CID assigned."); + + return process_vsock_cid(cid, port); +} + static int run(int argc, char* argv[]) { log_setup(); @@ -135,7 +185,7 @@ static int run(int argc, char* argv[]) { const char *p = startswith(host, "vsock/"); if (p) - return process_vsock(p, port); + return process_vsock_string(p, port); p = startswith(host, "unix/"); if (p) @@ -145,6 +195,10 @@ static int run(int argc, char* argv[]) { if (p) return process_vsock_mux(p, port); + p = startswith(host, "machine/"); + if (p) + return process_machine(p, port); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Don't know how to parse host name specification: %s", host); } diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index c5b4cb4e8c..96597b1d9d 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -6,6 +6,7 @@ #include "pretty-print.h" #include "reboot-util.h" #include "systemctl-compat-shutdown.h" +#include "systemctl-logind.h" #include "systemctl-sysv-compat.h" #include "systemctl.h" #include "terminal-util.h" @@ -137,7 +138,7 @@ int shutdown_parse_argv(int argc, char *argv[]) { return r; } } else - arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE; + arg_when = USEC_INFINITY; /* logind chooses on server side */ if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN) /* No time argument for shutdown cancel */ diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c index d6cdd9748f..87e96a3a17 100644 --- a/src/systemctl/systemctl-logind.c +++ b/src/systemctl/systemctl-logind.c @@ -408,7 +408,7 @@ int logind_show_shutdown(void) { else /* If we don't recognize the action string, we'll show it as-is */ pretty_action = action; - if (arg_action == ACTION_SYSTEMCTL) + if (IN_SET(arg_action, ACTION_SYSTEMCTL, ACTION_SYSTEMCTL_SHOW_SHUTDOWN)) log_info("%s scheduled for %s, use 'systemctl %s --when=cancel' to cancel.", pretty_action, FORMAT_TIMESTAMP_STYLE(elapse, arg_timestamp_style), diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index 2fdf321886..50f30d8565 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -202,11 +202,13 @@ typedef struct UnitStatusInfo { bool transient; /* Service */ + bool running; pid_t main_pid; pid_t control_pid; - const char *status_text; const char *pid_file; - bool running; + const char *status_text; + const char *status_bus_error; + const char *status_varlink_error; int status_errno; uint32_t fd_store_max; @@ -681,9 +683,26 @@ static void print_status_info( if (i->status_text) printf(" Status: \"%s%s%s\"\n", ansi_highlight_cyan(), i->status_text, ansi_normal()); - if (i->status_errno > 0) { - errno = i->status_errno; - printf(" Error: %i (%m)\n", i->status_errno); + + if (i->status_errno > 0 || i->status_bus_error || i->status_varlink_error) { + const char *prefix = " "; + + printf(" Error:"); + + if (i->status_errno > 0) { + printf("%scode: %i (%s)", prefix, i->status_errno, STRERROR(i->status_errno)); + prefix = "; "; + } + if (i->status_bus_error) { + printf("%sD-Bus: %s", prefix, i->status_bus_error); + prefix = "; "; + } + if (i->status_varlink_error) { + printf("%sVarlink: %s", prefix, i->status_varlink_error); + prefix = "; "; + } + + putchar('\n'); } if (i->ip_ingress_bytes != UINT64_MAX && i->ip_egress_bytes != UINT64_MAX) @@ -2041,9 +2060,11 @@ static int show_one( { "ExecMainPID", "u", NULL, offsetof(UnitStatusInfo, main_pid) }, { "MainPID", "u", map_main_pid, 0 }, { "ControlPID", "u", NULL, offsetof(UnitStatusInfo, control_pid) }, - { "StatusText", "s", NULL, offsetof(UnitStatusInfo, status_text) }, { "PIDFile", "s", NULL, offsetof(UnitStatusInfo, pid_file) }, + { "StatusText", "s", NULL, offsetof(UnitStatusInfo, status_text) }, { "StatusErrno", "i", NULL, offsetof(UnitStatusInfo, status_errno) }, + { "StatusBusError", "s", NULL, offsetof(UnitStatusInfo, status_bus_error) }, + { "StatusVarlinkError", "s", NULL, offsetof(UnitStatusInfo, status_varlink_error) }, { "FileDescriptorStoreMax", "u", NULL, offsetof(UnitStatusInfo, fd_store_max) }, { "NFileDescriptorStore", "u", NULL, offsetof(UnitStatusInfo, n_fd_store) }, { "ExecMainStartTimestamp", "t", NULL, offsetof(UnitStatusInfo, start_timestamp) }, diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index 95cf00fc81..00dd05bff7 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -203,10 +203,8 @@ int verb_start_special(int argc, char *argv[], void *userdata) { case ACTION_SOFT_REBOOT: if (arg_when == 0) r = logind_reboot(a); - else if (arg_when != USEC_INFINITY) + else r = logind_schedule_shutdown(a); - else /* arg_when == USEC_INFINITY */ - r = logind_cancel_shutdown(); if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS)) /* The latter indicates that the requested operation requires auth, * is not supported or already in progress, in which cases we ignore the error. */ diff --git a/src/systemctl/systemctl-util.c b/src/systemctl/systemctl-util.c index be3b35e6f9..a73fc3afd0 100644 --- a/src/systemctl/systemctl-util.c +++ b/src/systemctl/systemctl-util.c @@ -6,6 +6,7 @@ #include "sd-bus.h" #include "sd-daemon.h" +#include "ask-password-agent.h" #include "bus-common-errors.h" #include "bus-locator.h" #include "bus-map-properties.h" @@ -19,11 +20,10 @@ #include "macro.h" #include "path-util.h" #include "pidref.h" +#include "polkit-agent.h" #include "process-util.h" #include "reboot-util.h" #include "set.h" -#include "spawn-ask-password-agent.h" -#include "spawn-polkit-agent.h" #include "stat-util.h" #include "systemctl-util.h" #include "systemctl.h" diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 0ca76ac23d..5bb6ccacf7 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1023,15 +1023,17 @@ static int systemctl_parse_argv(int argc, char *argv[]) { case ARG_WHEN: if (streq(optarg, "show")) { - r = logind_show_shutdown(); - if (r < 0 && r != -ENODATA) - return r; - - return 0; + arg_action = ACTION_SYSTEMCTL_SHOW_SHUTDOWN; + return 1; } if (STR_IN_SET(optarg, "", "cancel")) { - arg_when = USEC_INFINITY; + arg_action = ACTION_CANCEL_SHUTDOWN; + return 1; + } + + if (streq(optarg, "auto")) { + arg_when = USEC_INFINITY; /* logind chooses on server side */ break; } @@ -1339,6 +1341,7 @@ static int run(int argc, char *argv[]) { break; case ACTION_SHOW_SHUTDOWN: + case ACTION_SYSTEMCTL_SHOW_SHUTDOWN: r = logind_show_shutdown(); break; diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index cc2b8c2cc4..00405f4705 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -35,6 +35,7 @@ enum action { ACTION_RUNLEVEL, ACTION_CANCEL_SHUTDOWN, ACTION_SHOW_SHUTDOWN, + ACTION_SYSTEMCTL_SHOW_SHUTDOWN, _ACTION_MAX, _ACTION_INVALID = -EINVAL, }; diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h index 16e9986be3..f4f4e95b7f 100644 --- a/src/systemd/sd-messages.h +++ b/src/systemd/sd-messages.h @@ -209,6 +209,8 @@ _SD_BEGIN_DECLARATIONS; #define SD_MESSAGE_POWER_KEY_STR SD_ID128_MAKE_STR(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,71) #define SD_MESSAGE_POWER_KEY_LONG_PRESS SD_ID128_MAKE(3e,01,17,10,1e,b2,43,c1,b9,a5,0d,b3,49,4a,b1,0b) #define SD_MESSAGE_POWER_KEY_LONG_PRESS_STR SD_ID128_MAKE_STR(3e,01,17,10,1e,b2,43,c1,b9,a5,0d,b3,49,4a,b1,0b) +#define SD_MESSAGE_SECURE_ATTENTION_KEY_PRESS SD_ID128_MAKE(b2,bc,ba,f5,ed,f9,48,e0,93,ce,50,bb,ea,0e,81,ec) +#define SD_MESSAGE_SECURE_ATTENTION_KEY_PRESS_STR SD_ID128_MAKE_STR(b2,bc,ba,f5,ed,f9,48,e0,93,ce,50,bb,ea,0e,81,ec) #define SD_MESSAGE_REBOOT_KEY SD_ID128_MAKE(9f,a9,d2,c0,12,13,4e,c3,85,45,1f,fe,31,6f,97,d0) #define SD_MESSAGE_REBOOT_KEY_STR SD_ID128_MAKE_STR(9f,a9,d2,c0,12,13,4e,c3,85,45,1f,fe,31,6f,97,d0) #define SD_MESSAGE_REBOOT_KEY_LONG_PRESS SD_ID128_MAKE(f1,c5,9a,58,c9,d9,43,66,89,65,c3,37,ca,ec,59,75) diff --git a/src/test/test-cgroup.c b/src/test/test-cgroup.c index 8bd4af94e4..040e9e9c12 100644 --- a/src/test/test-cgroup.c +++ b/src/test/test-cgroup.c @@ -159,6 +159,8 @@ TEST(id) { if (ERRNO_IS_NEG_PRIVILEGE(fd2)) log_notice("Skipping open-by-cgroup-id test because lacking privs."); + else if (ERRNO_IS_NEG_NOT_SUPPORTED(fd2)) + log_notice("Skipping open-by-cgroup-id test because syscall is missing or blocked."); else { assert_se(fd2 >= 0); diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 7b16cf2258..928afefe71 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -293,7 +293,7 @@ TEST(copy_tree_at_symlink) { TEST_RET(copy_bytes) { _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; _cleanup_close_ int infd = -EBADF; - int r, r2; + int r; char buf[1024], buf2[1024]; infd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); @@ -307,14 +307,14 @@ TEST_RET(copy_bytes) { r = copy_bytes(infd, pipefd[1], UINT64_MAX, 0); assert_se(r == 0); - r = read(pipefd[0], buf, sizeof(buf)); - assert_se(r >= 0); + ssize_t n = read(pipefd[0], buf, sizeof(buf)); + assert_se(n >= 0); assert_se(lseek(infd, 0, SEEK_SET) == 0); - r2 = read(infd, buf2, sizeof(buf2)); - assert_se(r == r2); + ssize_t n2 = read(infd, buf2, sizeof(buf2)); + assert_se(n == n2); - assert_se(strneq(buf, buf2, r)); + assert_se(strneq(buf, buf2, n)); /* test copy_bytes with invalid descriptors */ r = copy_bytes(pipefd[0], pipefd[0], 1, 0); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 4b8daa46bb..56f5e340be 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -832,6 +832,8 @@ static void test_exec_systemcallfilter(Manager *m) { return; } + test(m, "exec-systemcallfilter-writing-handoff-timestamp.service", 0, CLD_EXITED); + test(m, "exec-systemcallfilter-not-failing.service", 0, CLD_EXITED); test(m, "exec-systemcallfilter-not-failing2.service", 0, CLD_EXITED); test(m, "exec-systemcallfilter-not-failing3.service", 0, CLD_EXITED); diff --git a/src/test/test-json.c b/src/test/test-json.c index 69fb0859c7..aea4bbaf3e 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -306,13 +306,17 @@ TEST(build) { a = sd_json_variant_unref(a); b = sd_json_variant_unref(b); - assert_se(sd_json_build(&a, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("one", SD_JSON_BUILD_INTEGER(7)), - SD_JSON_BUILD_PAIR("two", SD_JSON_BUILD_REAL(2.0)), - SD_JSON_BUILD_PAIR("three", SD_JSON_BUILD_INTEGER(0)))) >= 0); + assert_se(sd_json_buildo(&a, + SD_JSON_BUILD_PAIR("one", SD_JSON_BUILD_INTEGER(7)), + SD_JSON_BUILD_PAIR("two", SD_JSON_BUILD_REAL(2.0)), + SD_JSON_BUILD_PAIR("four", JSON_BUILD_STRING_UNDERSCORIFY("foo-bar-baz")), + SD_JSON_BUILD_PAIR("three", SD_JSON_BUILD_INTEGER(0))) >= 0); - assert_se(sd_json_build(&b, SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("two", SD_JSON_BUILD_INTEGER(2)), - SD_JSON_BUILD_PAIR("three", SD_JSON_BUILD_REAL(0)), - SD_JSON_BUILD_PAIR("one", SD_JSON_BUILD_REAL(7)))) >= 0); + assert_se(sd_json_buildo(&b, + SD_JSON_BUILD_PAIR("two", SD_JSON_BUILD_INTEGER(2)), + SD_JSON_BUILD_PAIR("four", SD_JSON_BUILD_STRING("foo_bar_baz")), + SD_JSON_BUILD_PAIR("three", SD_JSON_BUILD_REAL(0)), + SD_JSON_BUILD_PAIR("one", SD_JSON_BUILD_REAL(7))) >= 0); assert_se(sd_json_variant_equal(a, b)); @@ -913,37 +917,40 @@ TEST(json_dispatch) { } typedef enum mytestenum { - myfoo, mybar, mybaz, _mymax, _myinvalid = -EINVAL, + myfoo, mybar, mybaz, with_some_dashes, _mymax, _myinvalid = -EINVAL, } mytestenum; static const char *mytestenum_table[_mymax] = { [myfoo] = "myfoo", [mybar] = "mybar", [mybaz] = "mybaz", + [with_some_dashes] = "with-some-dashes", }; -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(mytestenum, mytestenum); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(mytestenum, mytestenum); static JSON_DISPATCH_ENUM_DEFINE(dispatch_mytestenum, mytestenum, mytestenum_from_string); TEST(json_dispatch_enum_define) { struct data { - mytestenum a, b, c, d; + mytestenum a, b, c, d, e; } data = { .a = _myinvalid, .b = _myinvalid, .c = _myinvalid, .d = mybar, + .e = _myinvalid, }; _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; - assert_se(sd_json_build(&j, SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_STRING("mybaz")), - SD_JSON_BUILD_PAIR("b", SD_JSON_BUILD_STRING("mybar")), - SD_JSON_BUILD_PAIR("c", SD_JSON_BUILD_STRING("myfoo")), - SD_JSON_BUILD_PAIR("d", SD_JSON_BUILD_NULL))) >= 0); + assert_se(sd_json_buildo(&j, + SD_JSON_BUILD_PAIR("a", SD_JSON_BUILD_STRING("mybaz")), + SD_JSON_BUILD_PAIR("b", SD_JSON_BUILD_STRING("mybar")), + SD_JSON_BUILD_PAIR("c", SD_JSON_BUILD_STRING("myfoo")), + SD_JSON_BUILD_PAIR("d", SD_JSON_BUILD_NULL), + SD_JSON_BUILD_PAIR("e", JSON_BUILD_STRING_UNDERSCORIFY(mytestenum_to_string(with_some_dashes)))) >= 0); assert_se(sd_json_dispatch(j, (const sd_json_dispatch_field[]) { @@ -951,6 +958,7 @@ TEST(json_dispatch_enum_define) { { "b", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, b), 0 }, { "c", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, c), 0 }, { "d", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, d), 0 }, + { "e", _SD_JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, e), 0 }, {}, }, /* flags= */ 0, @@ -960,6 +968,7 @@ TEST(json_dispatch_enum_define) { assert(data.b == mybar); assert(data.c == myfoo); assert(data.d < 0); + assert(data.e == with_some_dashes); } TEST(json_dispatch_double) { diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index 8f0f6e701c..516bddefbe 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -253,9 +253,9 @@ TEST(passfd_read) { assert_se(receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd) == 0); assert_se(fd >= 0); - r = read(fd, buf, sizeof(buf)-1); - assert_se(r >= 0); - buf[r] = 0; + ssize_t n = read(fd, buf, sizeof(buf)-1); + assert_se(n >= 0); + buf[n] = 0; ASSERT_STREQ(buf, file_contents); } diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index dbd7654991..9f8ca15a65 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -176,4 +176,37 @@ TEST(get_default_background_color) { log_notice("R=%g G=%g B=%g", red, green, blue); } +static void test_get_color_mode_with_env(const char *key, const char *val, ColorMode expected) { + ASSERT_OK(setenv(key, val, true)); + reset_terminal_feature_caches(); + log_info("get_color_mode($%s=%s): %s", key, val, color_mode_to_string(get_color_mode())); + ASSERT_EQ(get_color_mode(), expected); +} + +TEST(get_color_mode) { + log_info("get_color_mode(default): %s", color_mode_to_string(get_color_mode())); + ASSERT_OK(get_color_mode()); + + test_get_color_mode_with_env("SYSTEMD_COLORS", "0", COLOR_OFF); + test_get_color_mode_with_env("SYSTEMD_COLORS", "no", COLOR_OFF); + test_get_color_mode_with_env("SYSTEMD_COLORS", "16", COLOR_16); + test_get_color_mode_with_env("SYSTEMD_COLORS", "256", COLOR_256); + test_get_color_mode_with_env("SYSTEMD_COLORS", "1", COLOR_24BIT); + test_get_color_mode_with_env("SYSTEMD_COLORS", "yes", COLOR_24BIT); + test_get_color_mode_with_env("SYSTEMD_COLORS", "24bit", COLOR_24BIT); + + ASSERT_OK(setenv("NO_COLOR", "1", true)); + test_get_color_mode_with_env("SYSTEMD_COLORS", "42", COLOR_OFF); + test_get_color_mode_with_env("SYSTEMD_COLORS", "invalid", COLOR_OFF); + ASSERT_OK(unsetenv("NO_COLOR")); + ASSERT_OK(unsetenv("SYSTEMD_COLORS")); + + test_get_color_mode_with_env("COLORTERM", "truecolor", terminal_is_dumb() ? COLOR_OFF : COLOR_24BIT); + test_get_color_mode_with_env("COLORTERM", "24bit", terminal_is_dumb() ? COLOR_OFF : COLOR_24BIT); + test_get_color_mode_with_env("COLORTERM", "invalid", terminal_is_dumb() ? COLOR_OFF : COLOR_256); + test_get_color_mode_with_env("COLORTERM", "42", terminal_is_dumb() ? COLOR_OFF : COLOR_256); + unsetenv("COLORTERM"); + reset_terminal_feature_caches(); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index f14622bd58..c2d385eb56 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -10,6 +10,7 @@ #include "varlink-io.systemd.h" #include "varlink-io.systemd.BootControl.h" #include "varlink-io.systemd.Credentials.h" +#include "varlink-io.systemd.Import.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.ManagedOOM.h" #include "varlink-io.systemd.MountFileSystem.h" @@ -182,6 +183,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_BootControl); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_Import); + print_separator(); test_parse_format_one(&vl_interface_xyz_test); } @@ -261,6 +264,17 @@ TEST(field_name_is_valid) { assert_se(varlink_idl_field_name_is_valid("foo0foo")); } +TEST(qualified_symbol_name_is_valid) { + assert_se(varlink_idl_qualified_symbol_name_is_valid(NULL) == 0); + assert_se(varlink_idl_qualified_symbol_name_is_valid("") == 0); + assert_se(varlink_idl_qualified_symbol_name_is_valid("x") == 0); + assert_se(varlink_idl_qualified_symbol_name_is_valid("xxx") == 0); + assert_se(varlink_idl_qualified_symbol_name_is_valid("xxx.xxx") == 0); + assert_se(varlink_idl_qualified_symbol_name_is_valid("xxx.Xxx") > 0); + assert_se(varlink_idl_qualified_symbol_name_is_valid("xxx.xxx.XXX") > 0); + assert_se(varlink_idl_qualified_symbol_name_is_valid("xxx.xxx.0foo") == 0); +} + TEST(validate_json) { _cleanup_(varlink_interface_freep) VarlinkInterface *parsed = NULL; @@ -268,7 +282,11 @@ TEST(validate_json) { /* This one has (nested) enonymous enums and structs */ static const char text[] = "interface validate.test\n" - "method Mymethod ( a:string, b:int, c:?bool, d:[]int, e:?[string]bool, f:?(piff, paff), g:(f:float) ) -> ()\n"; + "method Mymethod ( \n" + "# piff \n" + "a:string,\n" + "#paff\n" + "b:int, c:?bool, d:[]int, e:?[string]bool, f:?(piff, paff), g:(f:float) ) -> ()\n"; assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0); test_parse_format_one(parsed); diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 46ec6b31bc..2e19b28a6d 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -19,9 +19,9 @@ #include "main-func.h" #include "pager.h" #include "parse-util.h" +#include "polkit-agent.h" #include "pretty-print.h" #include "sparse-endian.h" -#include "spawn-polkit-agent.h" #include "string-table.h" #include "strv.h" #include "terminal-util.h" diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 5841db293e..59b48492ab 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -3610,17 +3610,6 @@ static int parse_line( assert(buffer); const Specifier specifier_table[] = { - { 'a', specifier_architecture, NULL }, - { 'b', specifier_boot_id, NULL }, - { 'B', specifier_os_build_id, NULL }, - { 'H', specifier_hostname, NULL }, - { 'l', specifier_short_hostname, NULL }, - { 'm', specifier_machine_id, NULL }, - { 'o', specifier_os_id, NULL }, - { 'v', specifier_kernel_release, NULL }, - { 'w', specifier_os_version_id, NULL }, - { 'W', specifier_os_variant_id, NULL }, - { 'h', specifier_user_home, NULL }, { 'C', specifier_directory, UINT_TO_PTR(DIRECTORY_CACHE) }, @@ -3628,6 +3617,7 @@ static int parse_line( { 'S', specifier_directory, UINT_TO_PTR(DIRECTORY_STATE) }, { 't', specifier_directory, UINT_TO_PTR(DIRECTORY_RUNTIME) }, + COMMON_SYSTEM_SPECIFIERS, COMMON_CREDS_SPECIFIERS(arg_runtime_scope), COMMON_TMP_SPECIFIERS, {} diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 02caf218e7..f4e5cc2cbf 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -22,6 +22,9 @@ static PagerFlags arg_pager_flags = 0; static VarlinkMethodFlags arg_method_flags = 0; static bool arg_collect = false; static bool arg_quiet = false; +static char **arg_graceful = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_graceful, strv_freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -58,6 +61,7 @@ static int help(void) { " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short otherwise\n" " -q --quiet Do not output method reply\n" + " --graceful=ERROR Treat specified Varlink error as success\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -82,6 +86,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_ONEWAY, ARG_JSON, ARG_COLLECT, + ARG_GRACEFUL, }; static const struct option options[] = { @@ -93,6 +98,7 @@ static int parse_argv(int argc, char *argv[]) { { "json", required_argument, NULL, ARG_JSON }, { "collect", no_argument, NULL, ARG_COLLECT }, { "quiet", no_argument, NULL, 'q' }, + { "graceful", required_argument, NULL, ARG_GRACEFUL }, {}, }; @@ -142,6 +148,18 @@ static int parse_argv(int argc, char *argv[]) { arg_quiet = true; break; + case ARG_GRACEFUL: + r = varlink_idl_qualified_symbol_name_is_valid(optarg); + if (r < 0) + return log_error_errno(r, "Failed to validate Varlink error name '%s': %m", optarg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid Varlink error name: %s", optarg); + + if (strv_extend(&arg_graceful, optarg) < 0) + return log_oom(); + + break; + case '?': return -EINVAL; @@ -153,6 +171,8 @@ static int parse_argv(int argc, char *argv[]) { if (FLAGS_SET(arg_method_flags, VARLINK_METHOD_MORE)) arg_json_format_flags |= SD_JSON_FORMAT_SEQ; + strv_sort_uniq(arg_graceful); + return 1; } @@ -438,7 +458,13 @@ static int reply_callback( /* Propagate the error we received via sd_notify() */ (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error); - r = *ret = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error); + if (strv_contains(arg_graceful, error)) { + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "Method call returned expected error: %s", error); + + r = 0; + } else + r = *ret = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error); } else r = 0; @@ -505,7 +531,13 @@ static int verb_call(int argc, char *argv[], void *userdata) { /* Propagate the error we received via sd_notify() */ (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error); - r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error); + if (strv_contains(arg_graceful, error)) { + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "Method call %s() returned expected error: %s", method, error); + + r = 0; + } else + r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error); } else r = 0; @@ -570,7 +602,13 @@ static int verb_call(int argc, char *argv[], void *userdata) { /* Propagate the error we received via sd_notify() */ (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error); - r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error); + if (strv_contains(arg_graceful, error)) { + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "Method call %s() returned expected error: %s", method, error); + + r = 0; + } else + r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error); } else r = 0; diff --git a/src/vmspawn/vmspawn-register.c b/src/vmspawn/vmspawn-register.c index 2140bba9cd..124ee111b2 100644 --- a/src/vmspawn/vmspawn-register.c +++ b/src/vmspawn/vmspawn-register.c @@ -22,7 +22,8 @@ int register_machine( const char *directory, unsigned cid, const char *address, - const char *key_path) { + const char *key_path, + bool keep_unit) { _cleanup_(varlink_unrefp) Varlink *vl = NULL; int r; @@ -70,7 +71,9 @@ int register_machine( SD_JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(cid), "vSockCid", SD_JSON_BUILD_UNSIGNED(cid)), SD_JSON_BUILD_PAIR_CONDITION(!!directory, "rootDirectory", SD_JSON_BUILD_STRING(directory)), SD_JSON_BUILD_PAIR_CONDITION(!!address, "sshAddress", SD_JSON_BUILD_STRING(address)), - SD_JSON_BUILD_PAIR_CONDITION(!!key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(key_path))); + SD_JSON_BUILD_PAIR_CONDITION(!!key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(key_path)), + SD_JSON_BUILD_PAIR_CONDITION(isatty(STDIN_FILENO), "allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(!keep_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true))); } int unregister_machine(sd_bus *bus, const char *machine_name) { diff --git a/src/vmspawn/vmspawn-register.h b/src/vmspawn/vmspawn-register.h index 69f56714b2..d46e28c792 100644 --- a/src/vmspawn/vmspawn-register.h +++ b/src/vmspawn/vmspawn-register.h @@ -11,5 +11,7 @@ int register_machine( const char *directory, unsigned cid, const char *address, - const char *key_path); + const char *key_path, + bool keep_unit); + int unregister_machine(sd_bus *bus, const char *machine_name); diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 48aca0ee57..b0e1ea3c0b 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -34,7 +34,7 @@ #if defined(__x86_64__) || defined(__i386__) # define QEMU_MACHINE_TYPE "q35" -#elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) +#elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) # define QEMU_MACHINE_TYPE "virt" #elif defined(__s390__) || defined(__s390x__) # define QEMU_MACHINE_TYPE "s390-ccw-virtio" diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 9dc1514ba7..584e8062b0 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -107,6 +107,7 @@ static char *arg_forward_journal = NULL; static bool arg_runtime_directory_created = false; static bool arg_privileged = false; static bool arg_register = false; +static bool arg_keep_unit = false; static sd_id128_t arg_uuid = {}; static char **arg_kernel_cmdline_extra = NULL; static char **arg_extra_drives = NULL; @@ -170,6 +171,7 @@ static int help(void) { " --uuid=UUID Set a specific machine UUID for the VM\n" "\n%3$sProperties:%4$s\n" " --register=BOOLEAN Register VM with systemd-machined\n" + " --keep-unit Don't let systemd-machined allocate scope unit for us\n" "\n%3$sUser Namespacing:%4$s\n" " --private-users=UIDBASE[:NUIDS]\n" " Configure the UID/GID range to map into the\n" @@ -235,6 +237,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NETWORK_USER_MODE, ARG_UUID, ARG_REGISTER, + ARG_KEEP_UNIT, ARG_BIND, ARG_BIND_RO, ARG_EXTRA_DRIVE, @@ -277,6 +280,7 @@ static int parse_argv(int argc, char *argv[]) { { "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE }, { "uuid", required_argument, NULL, ARG_UUID }, { "register", required_argument, NULL, ARG_REGISTER }, + { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, { "bind", required_argument, NULL, ARG_BIND }, { "bind-ro", required_argument, NULL, ARG_BIND_RO }, { "extra-drive", required_argument, NULL, ARG_EXTRA_DRIVE }, @@ -443,6 +447,11 @@ static int parse_argv(int argc, char *argv[]) { r = parse_boolean_argument("--register=", optarg, &arg_register); if (r < 0) return r; + + break; + + case ARG_KEEP_UNIT: + arg_keep_unit = true; break; case ARG_BIND: @@ -2058,7 +2067,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { arg_directory, child_cid, child_cid != VMADDR_CID_ANY ? vm_address : NULL, - ssh_private_key_path); + ssh_private_key_path, + arg_keep_unit); if (r < 0) return r; } @@ -2232,8 +2242,10 @@ static int verify_arguments(void) { if (!strv_isempty(arg_initrds) && !arg_linux) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=."); - if (arg_register && !arg_privileged) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "--register= requires root privileges, refusing."); + if (arg_keep_unit && arg_register && cg_pid_get_owner_uid(0, NULL) >= 0) + /* Save the user from accidentally registering either user-$SESSION.scope or user@.service. + * The latter is not technically a user session, but we don't need to labour the point. */ + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--keep-unit --register=yes may not be used when invoked from a user session."); return 0; } diff --git a/test/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/test.sh b/test/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/test.sh index 565ed8d35a..47333ef0de 100755 --- a/test/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/test.sh +++ b/test/TEST-80-NOTIFYACCESS/TEST-80-NOTIFYACCESS.units/test.sh @@ -54,10 +54,21 @@ sync_in b echo "toplevel again: $BASHPID" -systemd-notify --ready --status="OK" +systemd-notify --ready +systemd-notify "ERRNO=1" "BUSERROR=org.freedesktop.DBus.Error.InvalidArgs" "VARLINKERROR=org.varlink.service.InvalidParameter" + +sync_out e +sync_in f + +systemd-notify "ERRNO=bogus" "BUSERROR=草wwww" "VARLINKERROR=systemköttel" + +sync_out g +sync_in h + +systemd-notify --status="OK" systemd-notify "NOTIFYACCESS=none" systemd-notify --status="BOGUS3" -sync_out e +sync_out i exec sleep infinity diff --git a/test/integration-test-wrapper.py b/test/integration-test-wrapper.py index b6a16aa3ef..21ec00680f 100755 --- a/test/integration-test-wrapper.py +++ b/test/integration-test-wrapper.py @@ -61,6 +61,8 @@ def main(): print(f"TEST_NO_QEMU=1, skipping {args.name}", file=sys.stderr) exit(77) + keep_journal = os.getenv("TEST_SAVE_JOURNAL", "fail") + name = args.name + (f"-{i}" if (i := os.getenv("MESON_TEST_ITERATION")) else "") dropin = textwrap.dedent( @@ -152,11 +154,10 @@ def main(): result = subprocess.run(cmd) - if result.returncode in (args.exit_code, 77): - # Do not keep journal files for tests that don't fail. - if journal_file: - journal_file.unlink(missing_ok=True) + if journal_file and (keep_journal == "0" or (result.returncode in (args.exit_code, 77) and keep_journal == "fail")): + journal_file.unlink(missing_ok=True) + if result.returncode in (args.exit_code, 77): exit(0 if result.returncode == args.exit_code else 77) if journal_file: diff --git a/test/test-execute/exec-systemcallfilter-writing-handoff-timestamp.service b/test/test-execute/exec-systemcallfilter-writing-handoff-timestamp.service new file mode 100644 index 0000000000..3bf2a6470c --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-writing-handoff-timestamp.service @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test for SystemCallFilter + +[Service] +ExecStart=true +Type=oneshot +# For issue #33299 +SystemCallFilter=~@network-io +SystemCallFilter=~write +SystemCallErrorNumber=ENOSYS diff --git a/test/test-network/conf/25-ipv6-prefix-veth-static-route.network b/test/test-network/conf/25-ipv6-prefix-veth-static-route.network new file mode 100644 index 0000000000..a2ea7bff2c --- /dev/null +++ b/test/test-network/conf/25-ipv6-prefix-veth-static-route.network @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Match] +Name=veth99 + +[Network] +IPv6AcceptRA=true + +[Route] +Gateway=fe80::1034:56ff:fe78:9abd +GatewayOnLink=no +Metric=256 + +[IPv6AcceptRA] +RouteMetric=256 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 0355c7aca1..7c336ba9e2 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -5824,6 +5824,30 @@ class NetworkdRATests(unittest.TestCase, Utilities): self.assertIn('pref high', output) self.assertNotIn('pref low', output) + def test_ndisc_vs_static_route(self): + copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-static-route.network') + start_networkd() + self.wait_online('veth99:routable', 'veth-peer:degraded') + + output = check_output('ip -6 route show dev veth99 table all') + print(output) + + # If a conflicting static route is already configured, do not override the static route. + output = check_output('ip -6 route show dev veth99 default via fe80::1034:56ff:fe78:9abd') + print(output) + self.assertIn('default proto static metric 256 pref medium', output) + self.assertNotIn('proto ra', output) + + if not os.path.exists(test_ndisc_send): + self.skipTest(f"{test_ndisc_send} does not exist.") + + # Also check if the static route is protected from RA with zero lifetime + check_output(f'{test_ndisc_send} --interface veth-peer --type router-advertisement --lifetime 0') + time.sleep(2) + output = check_output('ip -6 route show dev veth99 default via fe80::1034:56ff:fe78:9abd') + print(output) + self.assertIn('default proto static metric 256 pref medium', output) + # radvd supports captive portal since v2.20. # https://github.com/radvd-project/radvd/commit/791179a7f730decbddb2290ef0e34aa85d71b1bc @unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals") diff --git a/test/units/TEST-13-NSPAWN.importctl.sh b/test/units/TEST-13-NSPAWN.importctl.sh index a13e3fd1fd..be31837447 100755 --- a/test/units/TEST-13-NSPAWN.importctl.sh +++ b/test/units/TEST-13-NSPAWN.importctl.sh @@ -9,10 +9,13 @@ set -o pipefail export PAGER= +TEST_CMDLINE="/tmp/proc-cmdline.$RANDOM" + at_exit() { set +e umount -l -R /var/lib/confexts - rm -f /var/tmp/importtest /var/tmp/importtest2 /var/tmp/importtest.tar.gz /var/tmp/importtest2.tar.gz + rm -f /var/tmp/importtest /var/tmp/importtest2 /var/tmp/importtest.tar.gz /var/tmp/importtest2.tar.gz "$TEST_CMDLINE" + mountpoint -q /proc/cmdline && umount /proc/cmdline } trap at_exit EXIT @@ -64,3 +67,19 @@ cmp /var/tmp/importtest /var/lib/confexts/importtest7/importtest importctl list-images importctl list-images -j + +varlinkctl call --more /run/systemd/io.systemd.Import io.systemd.Import.ListTransfers '{}' --graceful=io.systemd.Import.NoTransfers + +varlinkctl call --more /run/systemd/io.systemd.Import io.systemd.Import.Pull '{"class":"confext","remote":"file:///var/tmp/importtest.tar.gz","local":"importtest8","type":"tar","verify":"no"}' +cmp /var/tmp/importtest /var/lib/confexts/importtest8/importtest + +echo -n "systemd.pull=tar,confext,verify=no:importtest9:file:///var/tmp/importtest.tar.gz " > "$TEST_CMDLINE" +cat /proc/cmdline >> "$TEST_CMDLINE" +mount --bind "$TEST_CMDLINE" /proc/cmdline + +cat /proc/cmdline + +systemctl daemon-reload + +systemctl start import0.service +cmp /var/tmp/importtest /var/lib/confexts/importtest9/importtest diff --git a/test/units/TEST-13-NSPAWN.machinectl.sh b/test/units/TEST-13-NSPAWN.machinectl.sh index 462cc6a8c3..7ff953bae9 100755 --- a/test/units/TEST-13-NSPAWN.machinectl.sh +++ b/test/units/TEST-13-NSPAWN.machinectl.sh @@ -222,3 +222,6 @@ done (! machinectl read-only container1 "") (! machinectl read-only container1 foo) (! machinectl read-only container1 -- -1) + +varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{}' +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name":".host"}' diff --git a/test/units/TEST-22-TMPFILES.20.sh b/test/units/TEST-22-TMPFILES.20.sh new file mode 100755 index 0000000000..65d2b33161 --- /dev/null +++ b/test/units/TEST-22-TMPFILES.20.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# Test specifiers +set -eux + +rm -rf /tmp/specifiers + +root='/tmp/specifiers/root' +mkdir -p $root/etc +cat >$root/etc/os-release <<EOF +ID=the-id +BUILD_ID=build-id +VARIANT_ID=variant-id +VERSION_ID=version-id +IMAGE_ID=image-id +IMAGE_VERSION=22 +EOF + +systemd-tmpfiles --create - --root=$root <<EOF +f /os-release2 - - - - ID=%o\n +w+ /os-release2 - - - - BUILD_ID=%B\n +w+ /os-release2 - - - - VARIANT_ID=%W\n +w+ /os-release2 - - - - VERSION_ID=%w\n +w+ /os-release2 - - - - IMAGE_ID=%M\n +w+ /os-release2 - - - - IMAGE_VERSION=%A\n +EOF + +diff $root/etc/os-release $root/os-release2 diff --git a/test/units/TEST-50-DISSECT.mountfsd.sh b/test/units/TEST-50-DISSECT.mountfsd.sh index 604a9dbf38..0ab1a61079 100755 --- a/test/units/TEST-50-DISSECT.mountfsd.sh +++ b/test/units/TEST-50-DISSECT.mountfsd.sh @@ -84,7 +84,7 @@ if ! systemd-detect-virt -c; then -p DelegateSubgroup=supervisor \ -p Environment=SYSTEMD_LOG_LEVEL=debug \ --wait -- \ - systemd-nspawn --keep-unit -i /var/tmp/unpriv.raw --read-only --pipe echo thisisatest >/tmp/unpriv.out2 + systemd-nspawn --keep-unit --register=no -i /var/tmp/unpriv.raw --read-only --pipe echo thisisatest >/tmp/unpriv.out2 echo thisisatest | cmp /tmp/unpriv.out2 - fi diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index 8a014ac9fb..fc06fde0ee 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -1287,6 +1287,26 @@ testcase_dropped_partitions() { [[ "$(sfdisk -q -l "$image" | grep -c "$image")" -eq 2 ]] } +testcase_urandom() { + local workdir image defs + + workdir="$(mktemp --directory "/tmp/test-repart.urandom.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '${workdir:?}'" RETURN + + image="$workdir/image.img" + truncate -s 32M "$image" + + defs="$workdir/defs" + mkdir "$defs" + echo -ne "[Partition]\nType=swap\nCopyBlocks=/dev/urandom\n" >"$defs/10-urandom.conf" + + systemd-repart --empty=force --pretty=yes --dry-run=no --definitions="$defs" "$image" + + sfdisk -q -l "$image" + [[ "$(sfdisk -q -l "$image" | grep -c "$image")" -eq 1 ]] +} + OFFLINE="yes" run_testcases diff --git a/test/units/TEST-74-AUX-UTILS.bootctl.sh b/test/units/TEST-74-AUX-UTILS.bootctl.sh index 5331f5f8b4..1767f1fc89 100755 --- a/test/units/TEST-74-AUX-UTILS.bootctl.sh +++ b/test/units/TEST-74-AUX-UTILS.bootctl.sh @@ -264,7 +264,7 @@ EOF } testcase_bootctl_varlink() { - (varlinkctl call --collect /run/systemd/io.systemd.BootControl io.systemd.BootControl.ListBootEntries '{}' ||:) + varlinkctl call --collect /run/systemd/io.systemd.BootControl io.systemd.BootControl.ListBootEntries '{}' --graceful=io.systemd.BootControl.NoSuchBootEntry # We may have UEFI in the test environment. # If we don't have UEFI then we can test whether bootctl's varlink API fails cleanly. @@ -272,8 +272,8 @@ testcase_bootctl_varlink() { if ! (SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.GetRebootToFirmware '{}' || true) |& grep -q io.systemd.BootControl.RebootToFirmwareNotSupported; then return 0 fi - (SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.SetRebootToFirmware '{"state":true}' || true) |& grep -q io.systemd.BootControl.RebootToFirmwareNotSupported - (SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.SetRebootToFirmware '{"state":false}' || true) |& grep -q io.systemd.BootControl.RebootToFirmwareNotSupported + SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.SetRebootToFirmware '{"state":true}' --graceful=io.systemd.BootControl.RebootToFirmwareNotSupported + SYSTEMD_LOG_TARGET=console varlinkctl call --json=short /run/systemd/io.systemd.BootControl io.systemd.BootControl.SetRebootToFirmware '{"state":false}' --graceful=io.systemd.BootControl.RebootToFirmwareNotSupported } run_testcases diff --git a/test/units/TEST-74-AUX-UTILS.ssh.sh b/test/units/TEST-74-AUX-UTILS.ssh.sh index 5d87d9f7ac..482b6a1e28 100755 --- a/test/units/TEST-74-AUX-UTILS.ssh.sh +++ b/test/units/TEST-74-AUX-UTILS.ssh.sh @@ -53,6 +53,7 @@ mkdir -p /usr/share/empty.sshd /var/empty /var/empty/sshd /run/sshd ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" .host cat /etc/machine-id | cmp - /etc/machine-id ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" unix/run/ssh-unix-local/socket cat /etc/machine-id | cmp - /etc/machine-id +ssh -o StrictHostKeyChecking=no -v -i "$ROOTID" machine/.host cat /etc/machine-id | cmp - /etc/machine-id modprobe vsock_loopback ||: if test -e /dev/vsock -a -d /sys/module/vsock_loopback ; then diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 030f077c85..a0797469a4 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -43,9 +43,9 @@ if command -v userdbctl >/dev/null; then varlinkctl call -q /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{ "userName" : "testuser", "service" : "io.systemd.Multiplexer" }' varlinkctl call -j /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{ "userName" : "testuser", "service" : "io.systemd.Multiplexer" }' | jq . # We ignore the return value of the following two calls, since if no memberships are defined at all this will return a NotFound error, which is OK - (varlinkctl call --more /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' ||:) - (varlinkctl call --quiet --more /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' ||:) - (varlinkctl call --more -j /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' ||:) | jq --seq . + varlinkctl call --more /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' --graceful=io.systemd.UserDatabase.NoRecordFound + varlinkctl call --quiet --more /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' --graceful=io.systemd.UserDatabase.NoRecordFound + varlinkctl call --more -j /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' --graceful=io.systemd.UserDatabase.NoRecordFound | jq --seq . varlinkctl call --oneway /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' (! varlinkctl call --oneway /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' | grep .) fi @@ -75,7 +75,7 @@ rm_rf_sshbindir() { trap rm_rf_sshbindir EXIT -# Create a fake "ssh" binary that validates everything works as expected +# Create a fake "ssh" binary that validates everything works as expected if invoked for the "ssh-unix:" Varlink transport cat > "$SSHBINDIR"/ssh <<'EOF' #!/bin/sh @@ -87,10 +87,29 @@ test "$3" = "foobar" exec socat - UNIX-CONNECT:/run/systemd/journal/io.systemd.journal EOF +chmod +x "$SSHBINDIR"/ssh + +SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl info ssh-unix:foobar:/run/systemd/journal/io.systemd.journal + +# Now build another fake "ssh" binary that does the same for "ssh-exec:" +cat > "$SSHBINDIR"/ssh <<'EOF' +#!/bin/sh +set -xe + +test "$1" = "-e" +test "$2" = "none" +test "$3" = "-T" +test "$4" = "foobar" +test "$5" = "env" +test "$6" = "SYSTEMD_VARLINK_LISTEN=-" +test "$7" = "systemd-sysext" + +SYSTEMD_VARLINK_LISTEN=- exec systemd-sysext +EOF chmod +x "$SSHBINDIR"/ssh -SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl info ssh:foobar:/run/systemd/journal/io.systemd.journal +SYSTEMD_SSH="$SSHBINDIR/ssh" varlinkctl info ssh-exec:foobar:systemd-sysext # Go through all varlink sockets we can find under /run/systemd/ for some extra coverage find /run/systemd/ -name "io.systemd*" -type s | while read -r socket; do diff --git a/test/units/TEST-80-NOTIFYACCESS.sh b/test/units/TEST-80-NOTIFYACCESS.sh index 97b222a9e8..355f6e7e30 100755 --- a/test/units/TEST-80-NOTIFYACCESS.sh +++ b/test/units/TEST-80-NOTIFYACCESS.sh @@ -20,6 +20,8 @@ sync_out() { export SYSTEMD_LOG_LEVEL=debug +# Test NotifyAccess= override through sd_notify() + systemctl --no-block start notify.service sync_in a @@ -38,6 +40,22 @@ sync_out d sync_in e systemctl --quiet is-active notify.service +[[ "$(systemctl show notify.service -P StatusText)" != BOGUS* ]] + +assert_eq "$(systemctl show notify.service -P StatusErrno)" "1" +assert_eq "$(systemctl show notify.service -P StatusBusError)" "org.freedesktop.DBus.Error.InvalidArgs" +assert_eq "$(systemctl show notify.service -P StatusVarlinkError)" "org.varlink.service.InvalidParameter" + +sync_out f +sync_in g + +assert_eq "$(systemctl show notify.service -P StatusErrno)" "1" +assert_eq "$(systemctl show notify.service -P StatusBusError)" "org.freedesktop.DBus.Error.InvalidArgs" +assert_eq "$(systemctl show notify.service -P StatusVarlinkError)" "org.varlink.service.InvalidParameter" + +sync_out h +sync_in i + assert_eq "$(systemctl show notify.service -p StatusText --value)" "OK" assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "none" @@ -46,6 +64,12 @@ assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all" rm /tmp/syncfifo1 /tmp/syncfifo2 +# Explicitly test busctl's BUSERROR= reporting and systemctl status should show it + +(! systemd-run --wait --unit="TEST-80-BUSERROR.service" -p NotifyAccess=main busctl introspect org.freedesktop.systemd1 /bogus/001) +assert_eq "$(systemctl show TEST-80-BUSERROR.service -P StatusBusError)" "org.freedesktop.DBus.Error.UnknownObject" +assert_in "D-Bus: org.freedesktop.DBus.Error.UnknownObject" "$(systemctl status TEST-80-BUSERROR.service)" + # Now test basic fdstore behaviour MYSCRIPT="/tmp/myscript$RANDOM.sh" diff --git a/units/meson.build b/units/meson.build index b231341a1f..bdc34e6f2c 100644 --- a/units/meson.build +++ b/units/meson.build @@ -362,6 +362,11 @@ units = [ 'symlinks' : ['dbus-org.freedesktop.import1.service'], }, { + 'file' : 'systemd-importd.socket', + 'conditions' : ['ENABLE_IMPORTD'], + 'symlinks' : ['sockets.target.wants/'], + }, + { 'file' : 'systemd-initctl.service.in', 'conditions' : ['HAVE_SYSV_COMPAT'], }, diff --git a/units/systemd-importd.service.in b/units/systemd-importd.service.in index daa93776e1..e119d40929 100644 --- a/units/systemd-importd.service.in +++ b/units/systemd-importd.service.in @@ -8,9 +8,15 @@ # (at your option) any later version. [Unit] -Description=Virtual Machine and Container Download Service +Description=Disk Image Download Service Documentation=man:systemd-importd.service(8) Documentation=man:org.freedesktop.import1(5) +DefaultDependencies=no +After=systemd-importd.socket +WantsMountsFor=/var/lib/machines /var/lib/portables /var/lib/extensions /var/lib/confexts +After=systemd-remount-fs.service +Before=shutdown.target +Conflicts=shutdown.target [Service] Type=notify diff --git a/units/systemd-importd.socket b/units/systemd-importd.socket new file mode 100644 index 0000000000..d1c9c22a4c --- /dev/null +++ b/units/systemd-importd.socket @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Disk Image Download Service Socket +Documentation=man:systemd-importd.service(8) +Documentation=man:org.freedesktop.import1(5) +DefaultDependencies=no +WantsMountsFor=/var/lib/machines /var/lib/portables /var/lib/extensions /var/lib/confexts +After=systemd-remount-fs.service +Before=sockets.target +Conflicts=shutdown.target +Before=shutdown.target + +[Socket] +ListenStream=/run/systemd/io.systemd.Import +FileDescriptorName=varlink +SocketMode=0666 diff --git a/units/systemd-vmspawn@.service.in b/units/systemd-vmspawn@.service.in index 608002040c..fc4522ddb8 100644 --- a/units/systemd-vmspawn@.service.in +++ b/units/systemd-vmspawn@.service.in @@ -16,7 +16,7 @@ After=network.target modprobe@tun.service RequiresMountsFor=/var/lib/machines/%i [Service] -ExecStart=systemd-vmspawn --quiet --network-tap --machine=%i +ExecStart=systemd-vmspawn --quiet --register=yes --keep-unit --network-tap --machine=%i KillMode=mixed Type=notify Slice=machine.slice |