1
0
mirror of https://github.com/systemd/systemd synced 2026-03-06 21:24:46 +01:00

Compare commits

...

34 Commits

Author SHA1 Message Date
Yu Watanabe
4c6dd238b8
Merge pull request #18267 from lucaswerkmeister/truncate
Two StandardOutput=truncate:file improvements
2021-01-19 08:47:39 +09:00
Yu Watanabe
ceed428305
Merge pull request #18299 from ssahani/ensure-put
More use of hashmap_ensure_put and ordered_hashmap_ensure_put
2021-01-19 08:42:26 +09:00
Luca Boccassi
4db4792a93
Merge pull request #17228 from bluca/bind_path_runtime
core: add systemctl and DBUS method to bind mount new paths without service restart
2021-01-18 21:22:57 +00:00
Lucas Werkmeister
e372584067 man: document effects of concurrent truncation
Co-authored-by: Lennart Poettering <lennart@poettering.net>
2021-01-18 21:59:19 +01:00
Susant Sahani
df24a407a5 import: use - hashmap_ensure_put 2021-01-18 21:53:28 +01:00
Susant Sahani
630837065e core: execute - use hashmap_ensure_put 2021-01-18 21:53:25 +01:00
Susant Sahani
9b059ee022 core: dbus - use set_ensure_put 2021-01-18 21:53:22 +01:00
Susant Sahani
02288f3ef8 network: sd-dhcp6-client - use ordered_hashmap_ensure_put 2021-01-18 21:53:19 +01:00
Susant Sahani
5088e5e79f share: bus-unit-procs - use hashmap_ensure_put 2021-01-18 21:53:16 +01:00
Susant Sahani
ae4ad9df48 resolve: link - use hashmap_ensure_put 2021-01-18 21:53:13 +01:00
Susant Sahani
b5bcd73895 remount-fs: Use hashmap_ensure_put 2021-01-18 21:53:11 +01:00
Susant Sahani
acf56b72f2 core: transaction - use hashmap_ensure_put 2021-01-18 21:52:58 +01:00
Zbigniew Jędrzejewski-Szmek
4ea8b443de resolved: fix use-after-free with queries hitting the cache
When dns_transaction_complete() manages to resolve a query, it invalidates the
query candidate object. It shall not be accessed afterwards.

We have the following chain of calls:
dns_query_candidate_go → dns_transaction_go → dns_transaction_prepare → dns_cache_lookup (success: 1)
                                                                      → dns_transaction_complete
After returning back to dns_query_candidate_go(), we'd attempt to continue
iteration over the list of transactions attached to the query candidate,
accessing already freed (and overwritten) memory:

(gdb) bt
0  0x00007f637297cf47 in hashmap_iterate_entry (i=0x7ffe7e15cc90, h=0x706f746b73656465) at ../src/basic/hashmap.c:703
1  _hashmap_iterate (h=0x706f746b73656465, i=i@entry=0x7ffe7e15cc90, value=value@entry=0x7ffe7e15cc88,
    key=key@entry=0x0) at ../src/basic/hashmap.c:712
2  0x00007f637297d01b in set_iterate (s=<optimized out>, i=i@entry=0x7ffe7e15cc90, value=value@entry=0x7ffe7e15cc88)
    at ../src/basic/hashmap.c:733
hence we crash

3  0x0000557bc99eb80f in dns_query_candidate_go (c=c@entry=0x557bcaf86890) at ../src/resolve/resolved-dns-query.c:139
...but c is not valid here in the second iteration of the loop

4  0x0000557bc99eb720 in dns_query_candidate_notify (c=0x557bcaf86890) at ../src/resolve/resolved-dns-query.c:271
c was valid here at entry...

5  0x0000557bc99efe28 in dns_transaction_complete (t=0x557bcac072f0, state=<optimized out>)
    at ../src/resolve/resolved-dns-transaction.c:350
t is a valid transaction (11481 in the backtrace below)

6  0x0000557bc99f1efb in dns_transaction_process_reply (t=0x557bcac072f0, p=<optimized out>)
    at ../src/resolve/resolved-dns-transaction.c:1171
7  0x0000557bc99f2d41 in on_dns_packet (s=<optimized out>, fd=<optimized out>, revents=<optimized out>,
    userdata=0x557bcac072f0) at ../src/resolve/resolved-dns-transaction.c:1223
8  0x00007f6372a25217 in source_dispatch (s=s@entry=0x557bcb162c50) at ../src/libsystemd/sd-event/sd-event.c:3181
9  0x00007f6372a254fd in sd_event_dispatch (e=0x557bcb15b050) at ../src/libsystemd/sd-event/sd-event.c:3620
10 0x00007f6372a267c8 in sd_event_run (e=e@entry=0x557bcb15b050, timeout=timeout@entry=18446744073709551615)
    at ../src/libsystemd/sd-event/sd-event.c:3678
11 0x00007f6372a269ef in sd_event_loop (e=0x557bcb15b050) at ../src/libsystemd/sd-event/sd-event.c:3700
12 0x0000557bc99ddc14 in run (argc=<optimized out>, argv=<optimized out>) at ../src/resolve/resolved.c:92
13 0x0000557bc99d260a in main (argc=<optimized out>, argv=<optimized out>) at ../src/resolve/resolved.c:99

xxx.name.net systemd-resolved[31705]: Got message type=method_call sender=:1.3644 destination=org.freedesktop.resolve1 path=/org/freedesktop/resolve1 interface=org.freedesktop.resolve1.Manager member=ResolveHostname cookie=2 reply_cookie=0 signature=isit error-name=n/a error-message=n/a
xxx.name.net systemd-resolved[31705]: idn2_lookup_u8: xxx → xxx
xxx.name.net systemd-resolved[31705]: Looking up RR for xxx IN A.
xxx.name.net systemd-resolved[31705]: Sent message type=method_call sender=n/a destination=org.freedesktop.DBus path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=AddMatch cookie=1102 reply_cookie=0 signature=s error-name=n/a error-message=n/a
xxx.name.net systemd-resolved[31705]: Sent message type=method_call sender=n/a destination=org.freedesktop.DBus path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=GetNameOwner cookie=1103 reply_cookie=0 signature=s error-name=n/a error-message=n/a
xxx.name.net systemd-resolved[31705]: Got message type=method_return sender=org.freedesktop.DBus destination=:1.3324 path=n/a interface=n/a member=n/a cookie=4294967295 reply_cookie=1103 signature=s error-name=n/a error-message=n/a
xxx.name.net systemd-resolved[31705]: Cache miss for xxx.name.net IN A
xxx.name.net systemd-resolved[31705]: Transaction 11481 for <xxx.name.net IN A> scope dns on enp42s0/*.
xxx.name.net systemd-resolved[31705]: Using feature level UDP for transaction 11481.
xxx.name.net systemd-resolved[31705]: Using DNS server 192.168.1.1 for transaction 11481.
xxx.name.net systemd-resolved[31705]: Sending query packet with id 11481 of size 35.
xxx.name.net systemd-resolved[31705]: Got message type=method_return sender=org.freedesktop.DBus destination=:1.3324 path=n/a interface=n/a member=n/a cookie=4294967295 reply_cookie=1102 signature= error-name=n/a error-message=n/a
xxx.name.net systemd-resolved[31705]: Match type='signal',sender='org.freedesktop.DBus',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged',arg0=':1.3644' successfully installed.
xxx.name.net systemd-resolved[31705]: Processing incoming packet on transaction 11481 (rcode=NXDOMAIN).
xxx.name.net systemd-resolved[31705]: Not caching negative entry without a SOA record: xxx.name.net IN A
xxx.name.net systemd-resolved[31705]: Transaction 11481 for <xxx.name.net IN A> on scope dns on enp42s0/* now complete with <rcode-failure> from network (unsigned).
xxx.name.net systemd-resolved[31705]: Positive cache hit for xxx.lan IN A
xxx.name.net systemd-resolved[31705]: Transaction 64364 for <xxx.lan IN A> on scope dns on enp42s0/* now complete with <success> from cache (unsigned).
xxx.name.net systemd-resolved[31705]: Sent message type=method_return sender=n/a destination=:1.3644 path=n/a interface=n/a member=n/a cookie=1104 reply_cookie=2 signature=a(iiay)st error-name=n/a error-message=n/a
xxx.name.net systemd-resolved[31705]: Sent message type=method_call sender=n/a destination=org.freedesktop.DBus path=/org/freedesktop/DBus interface=org.freedesktop.DBus member=RemoveMatch cookie=1105 reply_cookie=0 signature=s error-name=n/a error-message=n/a
xxx.name.net systemd-resolved[31705]: Freeing transaction 64364.
xxx.name.net systemd[1]: systemd-resolved.service: Main process exited, code=dumped, status=11/SEGV
xxx.name.net systemd[1]: systemd-resolved.service: Failed with result 'core-dump'.

Fixes #16168, https://bugzilla.redhat.com/show_bug.cgi?id=1895937.
2021-01-18 21:45:03 +01:00
Susant Sahani
f334debabb sysusers: use ordered_hashmap_ensure_put 2021-01-18 21:37:39 +01:00
Susant Sahani
1b5d349788 sd-netlink: use hashmap_ensure_put 2021-01-18 21:35:31 +01:00
Susant Sahani
1d2d1654c6 varlink: use hashmap_ensure_put 2021-01-18 21:35:28 +01:00
Susant Sahani
ee9ead76bb network: wait-online - add a assert 2021-01-18 21:35:24 +01:00
Susant Sahani
3f70f938a0 network: wait-online - use hashmap_ensure_put 2021-01-18 21:35:20 +01:00
Susant Sahani
b2cb2e82c8 network: radv - use hashmap_ensure_put 2021-01-18 21:34:46 +01:00
Lennart Poettering
33295214fa update TODO 2021-01-18 21:05:32 +01:00
Luca Boccassi
fa7a3cd00e test: run strace with -f and copy log out 2021-01-18 17:24:05 +00:00
Luca Boccassi
a9d34376e6 test: skip missing optional libraries in image install
Not all optional libraries might be available on developers machines,
so log and skip.
Also some pkg-config files are broken (eg: tss2 on Debian Stable) so
skip if the required variables are missing, and improve logs.
2021-01-18 17:24:05 +00:00
Luca Boccassi
5e8deb94c6 core: add DBUS method to bind mount new nodes without service restart
Allow to setup new bind mounts for a service at runtime (via either
DBUS or a new 'systemctl bind' verb) with a new helper that forks into
the unit's mount namespace.
Add a new integration test to cover this.

Useful for zero-downtime addition to services that are running inside
mount namespaces, especially when using RootImage/RootDirectory.

If a service runs with a read-only root, a tmpfs is added on /run
to ensure we can create the airlock directory for incoming mounts
under /run/host/incoming.
2021-01-18 17:24:05 +00:00
Luca Boccassi
94293d65cd MountAPIVFS: always mount a tmpfs on /run
We need a writable /run for most operations, but in case a read-only
RootImage (or similar) is used, by default there's no additional
tmpfs mount on /run. Change this behaviour and document it.
2021-01-18 17:24:05 +00:00
Luca Boccassi
f7c18d3de8 machine: use file descriptor when chasing bind mount sources
Allows to always operate on pinned inodes, rather than paths, so that
races are less likely
2021-01-18 17:24:05 +00:00
Luca Boccassi
98f654fdea machine: enter target PID namespace when adding a live mount
machinectl fails since 21935150a0c42b91a322105f6a9129116bfc8e2e as it's now
mounting onto a file descriptor in a target namespace, without joining the
target's PID namespace.
Note that it's not enough to setns CLONE_NEWPID, but a double-fork is required
as well, as implemented by namespace_fork().

Add a test case to TEST-13-NSPAWN to cover this use case.
2021-01-18 17:24:05 +00:00
Luca Boccassi
2338a175fd shared/mount-util: use namespace_fork utils 2021-01-18 17:23:46 +00:00
Luca Boccassi
724e689715 machine: adjust error message to use 'normalized' instead of ../ 2021-01-18 17:23:12 +00:00
Luca Boccassi
6af52c3a45 machine/basic: factor out helper function to add airlocked mount to namespace 2021-01-18 17:23:11 +00:00
Susant Sahani
d1ce835989 network: bond - use ordered_set_ensure_put 2021-01-18 13:00:35 +01:00
Susant Sahani
becee4da44 journal: context - use hashmap_ensure_put 2021-01-18 12:16:34 +01:00
Susant Sahani
08ff6fcffb journal: context - Use _cleanup_free_ 2021-01-18 12:13:51 +01:00
Lucas Werkmeister
d15b1a6c22 man: document that truncate:file happens per command line 2021-01-15 20:22:29 +01:00
Lucas Werkmeister
58b65c6d6d test: fix exec-standardoutput-truncate test
The cmp in ExecStartPost= was actually failing – ExecStartPost= has the
same StandardOutput as the rest of the service, so the output file is
truncated before cmp can compare it with the expected output – but the
test still passed because test_exec_standardoutput_truncate() calls
test(), which only checks the main result, rather than test_service(),
which checks the result of the whole service. Fix the test by merging
the ExecStartPost= into the ExecStart= – the cmp has to be part of the
same command line as the cat so that the file is not truncated between
the two processes.
2021-01-15 20:22:29 +01:00
54 changed files with 838 additions and 382 deletions

10
NEWS
View File

@ -1,5 +1,15 @@
systemd System and Service Manager
CHANGES WITH 248:
* The MountAPIVFS= service file setting now additionally mounts a tmpfs
on /run/ if it is not already a mount point. A writable /run/ has always
been a requirement for a functioning system, but this was not
guaranteed when using a read-only image.
Users can always specify BindPaths= or InaccessiblePaths= as overrides,
and they will take precedence. If the host's root mount point is used,
there is no change in behaviour.
CHANGES WITH 247:
* KERNEL API INCOMPATIBILITY: Linux 4.14 introduced two new uevents

16
TODO
View File

@ -20,6 +20,22 @@ Janitorial Clean-ups:
Features:
* sd-boot: define a drop-in dir in the ESP that may contain X.509
certificates. If the firmware is detected to be in setup mode, automaticallly
enroll them as PK/KEK/db, turn off setup mode and proceed. Optionally,
instead of auto-enrolling them add them to the sd-boot menu, giving the user
the option to manually enroll them, after selecting the menu entry. This way,
installer images can just drop the certfiicates in the ESP, and on first boot
can easily enroll the keys without ever booting up.
* efi stub: optionally, load initrd from disk as a separate file, HMAC check it
with key from TPM, bound to PCR, refusing if failing. This would then allow
traditional distros that generate initrds locally to secure them with TPM:
after generating the initrd, do the HMAC calculation, put result in initrd
filename, done. This would then bind the validity of the initrd to the local
host, and used kernel, and means people cannot change initrd or kernel
without booting the kernel + initrd.
* importd: add ability download images for portabled + sysext
* importd: support image signature verification with PKCS#7 + OpenBSD signify

View File

@ -116,6 +116,11 @@ node /org/freedesktop/systemd1 {
SetUnitProperties(in s name,
in b runtime,
in a(sv) properties);
BindMountUnit(in s name,
in s source,
in s destination,
in b read_only,
in b mkdir);
RefUnit(in s name);
UnrefUnit(in s name);
StartTransientUnit(in s name,
@ -767,6 +772,8 @@ node /org/freedesktop/systemd1 {
<variablelist class="dbus-method" generated="True" extra-ref="SetUnitProperties()"/>
<variablelist class="dbus-method" generated="True" extra-ref="BindMountUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="RefUnit()"/>
<variablelist class="dbus-method" generated="True" extra-ref="UnrefUnit()"/>
@ -1156,6 +1163,9 @@ node /org/freedesktop/systemd1 {
the "Try" flavor is used in which case a service that isn't running is not affected by the restart. The
"ReloadOrRestart" flavors attempt a reload if the unit supports it and use a restart otherwise.</para>
<para><function>BindMountUnit()</function> can be used to bind mount new files or directories into
a running service mount namespace.</para>
<para><function>KillUnit()</function> may be used to kill (i.e. send a signal to) all processes of a
unit. It takes the unit <varname>name</varname>, an enum <varname>who</varname> and a UNIX
<varname>signal</varname> number to send. The <varname>who</varname> enum is one of
@ -2193,6 +2203,10 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
interface org.freedesktop.systemd1.Service {
methods:
BindMount(in s source,
in s destination,
in b read_only,
in b mkdir);
GetProcesses(out a(sus) processes);
AttachProcesses(in s subcgroup,
in au pids);
@ -3252,6 +3266,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.systemd1.Service"/>
<variablelist class="dbus-method" generated="True" extra-ref="BindMount()"/>
<variablelist class="dbus-method" generated="True" extra-ref="GetProcesses()"/>
<variablelist class="dbus-method" generated="True" extra-ref="AttachProcesses()"/>
@ -3810,6 +3826,17 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--End of Autogenerated section-->
<refsect2>
<title>Methods</title>
<para><function>BindMount()</function> implements the same operation as the respective method on the
<interfacename>Manager</interfacename> object (see above). However, this method operates on the service
object and hence does not take a unit name parameter. Invoking the methods directly on the Manager
object has the advantage of not requiring a <function>GetUnit()</function> call to get the unit object
for a specific unit name. Calling the methods on the Manager object is hence a round trip
optimization.</para>
</refsect2>
<refsect2>
<title>Properties</title>

View File

@ -550,6 +550,23 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
</listitem>
</varlistentry>
<varlistentry>
<term><command>bind</command> <replaceable>UNIT</replaceable> <replaceable>PATH</replaceable> [<replaceable>PATH</replaceable>]</term>
<listitem><para>Bind mounts a file or directory from the host into the specified unit's view. The first path
argument is the source file or directory on the host, the second path argument is the destination file or
directory in the unit's view. When the latter is omitted, the destination path in the unit's view is the same as
the source path on the host. When combined with the <option>--read-only</option> switch, a ready-only bind
mount is created. When combined with the <option>--mkdir</option> switch, the destination path is first created
before the mount is applied. Note that this option is currently only supported for units that run within a mount
namespace (e.g.: with <option>RootImage=</option>, <option>PrivateMounts=</option>, etc.). This command supports bind
mounting directories, regular files, device nodes, <constant>AF_UNIX</constant> socket nodes, as well as FIFOs.
The bind mount is ephemeral, and it is undone as soon as the current unit process exists.
Note that the namespace mentioned here, where the bind mount will be added to, is the one where the main service
process runs, as other processes run in distinct namespaces (e.g.: <option>ExecReload=</option>,
<option>ExecStartPre=</option>, etc.) </para></listitem>
</varlistentry>
<varlistentry>
<term><command>service-log-level</command> <replaceable>SERVICE</replaceable> [<replaceable>LEVEL</replaceable>]</term>
@ -2246,6 +2263,21 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
</listitem>
</varlistentry>
<varlistentry>
<term><option>--mkdir</option></term>
<listitem><para>When used with <command>bind</command>, creates the destination file or directory before
applying the bind mount. Note that even though the name of this option suggests that it is suitable only for
directories, this option also creates the destination file node to mount over if the object to mount is not
a directory, but a regular file, device node, socket or FIFO.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--read-only</option></term>
<listitem><para>When used with <command>bind</command>, creates a read-only bind mount.</para></listitem>
</varlistentry>
<xi:include href="user-system-options.xml" xpointer="host" />
<xi:include href="user-system-options.xml" xpointer="machine" />

View File

@ -275,15 +275,20 @@
<term><varname>MountAPIVFS=</varname></term>
<listitem><para>Takes a boolean argument. If on, a private mount namespace for the unit's processes is created
and the API file systems <filename>/proc/</filename>, <filename>/sys/</filename>, and <filename>/dev/</filename>
are mounted inside of it, unless they are already mounted. Note that this option has no effect unless used in
conjunction with <varname>RootDirectory=</varname>/<varname>RootImage=</varname> as these three mounts are
and the API file systems <filename>/proc/</filename>, <filename>/sys/</filename>, <filename>/dev/</filename> and
<filename>/run/</filename> (as an empty <literal>tmpfs</literal>) are mounted inside of it, unless they are
already mounted. Note that this option has no effect unless used in conjunction with
<varname>RootDirectory=</varname>/<varname>RootImage=</varname> as these four mounts are
generally mounted in the host anyway, and unless the root directory is changed, the private mount namespace
will be a 1:1 copy of the host's, and include these three mounts. Note that the <filename>/dev/</filename> file
will be a 1:1 copy of the host's, and include these four mounts. Note that the <filename>/dev/</filename> file
system of the host is bind mounted if this option is used without <varname>PrivateDevices=</varname>. To run
the service with a private, minimal version of <filename>/dev/</filename>, combine this option with
<varname>PrivateDevices=</varname>.</para>
<para>In order to allow propagating mounts at runtime in a safe manner, <filename>/run/systemd/propagate</filename>
on the host will be used to set up new mounts, and <filename>/run/host/incoming/</filename> in the private namespace
will be used as an intermediate step to store them before being moved to the final mount point.</para>
<xi:include href="system-only.xml" xpointer="singular"/></listitem>
</varlistentry>
@ -2425,8 +2430,19 @@ SystemCallErrorNumber=EPERM</programlisting>
</para>
<para><option>truncate:<replaceable>path</replaceable></option> is similar to
<option>file:<replaceable>path</replaceable></option> above, but it truncates the file when opening it.
</para>
<option>file:<replaceable>path</replaceable></option> above, but it truncates the file when opening
it. For units with multiple command lines, e.g. <varname>Type=oneshot</varname> services with
multiple <varname>ExecStart=</varname>, or services with <varname>ExecCondition=</varname>,
<varname>ExecStartPre=</varname> or <varname>ExecStartPost=</varname>, the output file is reopened
and therefore re-truncated for each command line. If the output file is truncated while another
process still has the file open, e.g. by an <varname>ExecReload=</varname> running concurrently with
an <varname>ExecStart=</varname>, and the other process continues writing to the file without
adjusting its offset, then the space between the file pointers of the two processes may be filled
with <constant>NUL</constant> bytes, producing a sparse file. Thus,
<option>truncate:<replaceable>path</replaceable></option> is typically only useful for units where
only one process runs at a time, such as services with a single <varname>ExecStart=</varname> and no
<varname>ExecStartPost=</varname>, <varname>ExecReload=</varname>, <varname>ExecStop=</varname> or
similar.</para>
<para><option>socket</option> connects standard output to a socket acquired via socket activation. The
semantics are similar to the same option of <varname>StandardInput=</varname>, see above.</para>

View File

@ -2198,6 +2198,8 @@ public_programs += executable(
'src/systemctl/systemctl-log-setting.h',
'src/systemctl/systemctl-logind.c',
'src/systemctl/systemctl-logind.h',
'src/systemctl/systemctl-mount.c',
'src/systemctl/systemctl-mount.h',
'src/systemctl/systemctl-preset-all.c',
'src/systemctl/systemctl-preset-all.h',
'src/systemctl/systemctl-reset-failed.c',

View File

@ -214,7 +214,7 @@ _systemctl () {
list-timers list-units list-unit-files poweroff
reboot rescue show-environment suspend get-default
is-system-running preset-all'
[FILE]='link switch-root'
[FILE]='link switch-root bind'
[TARGETS]='set-default'
[MACHINES]='list-machines'
[LOG_LEVEL]='log-level'

View File

@ -31,6 +31,7 @@
"reset-failed:Reset failed state for all, one, or more units"
"list-dependencies:Show unit dependency tree"
"clean:Remove configuration, state, cache, logs or runtime data of units"
"bind:Bind mount a path from the host into a unit's namespace"
)
local -a machine_commands=(
@ -378,6 +379,10 @@ done
_files
}
(( $+functions[_systemctl_bind] )) || _systemctl_bind() {
_files
}
# no systemctl completion for:
# [STANDALONE]='daemon-reexec daemon-reload default
# emergency exit halt kexec list-jobs list-units

View File

@ -16,6 +16,7 @@
#include "dbus-job.h"
#include "dbus-manager.h"
#include "dbus-scope.h"
#include "dbus-service.h"
#include "dbus-unit.h"
#include "dbus.h"
#include "env-util.h"
@ -725,6 +726,11 @@ static int method_set_unit_properties(sd_bus_message *message, void *userdata, s
return method_generic_unit_operation(message, userdata, error, bus_unit_method_set_properties, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
}
static int method_bind_mount_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Only add mounts on fully loaded units */
return method_generic_unit_operation(message, userdata, error, bus_service_method_bind_mount, GENERIC_UNIT_VALIDATE_LOADED);
}
static int method_ref_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
/* Only allow reffing of fully loaded units, and make sure reffing a unit loads it. */
return method_generic_unit_operation(message, userdata, error, bus_unit_method_ref, GENERIC_UNIT_LOAD|GENERIC_UNIT_VALIDATE_LOADED);
@ -2760,6 +2766,16 @@ const sd_bus_vtable bus_manager_vtable[] = {
NULL,,
method_set_unit_properties,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("BindMountUnit",
"sssbb",
SD_BUS_PARAM(name)
SD_BUS_PARAM(source)
SD_BUS_PARAM(destination)
SD_BUS_PARAM(read_only)
SD_BUS_PARAM(mkdir),
NULL,,
method_bind_mount_unit,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_NAMES("RefUnit",
"s",
SD_BUS_PARAM(name),

View File

@ -11,11 +11,15 @@
#include "dbus-manager.h"
#include "dbus-service.h"
#include "dbus-util.h"
#include "execute.h"
#include "exit-status.h"
#include "fd-util.h"
#include "fileio.h"
#include "locale-util.h"
#include "mount-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "selinux-access.h"
#include "service.h"
#include "signal-util.h"
#include "string-util.h"
@ -91,6 +95,79 @@ static int property_get_exit_status_set(
return sd_bus_message_close_container(reply);
}
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
int read_only, make_file_or_directory;
const char *dest, *src, *propagate_directory;
Unit *u = userdata;
ExecContext *c;
pid_t unit_pid;
int r;
assert(message);
assert(u);
if (!MANAGER_IS_SYSTEM(u->manager))
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Adding bind mounts at runtime is only supported for system managers.");
r = mac_selinux_unit_access_check(u, message, "start", error);
if (r < 0)
return r;
r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_file_or_directory);
if (r < 0)
return r;
if (!path_is_absolute(src) || !path_is_normalized(src))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized.");
if (isempty(dest))
dest = src;
else if (!path_is_absolute(dest) || !path_is_normalized(dest))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized.");
r = bus_verify_manage_units_async_full(
u,
"bind-mount",
CAP_SYS_ADMIN,
N_("Authentication is required to bind mount on '$(unit)'."),
true,
message,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
if (u->type != UNIT_SERVICE)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not of type .service");
/* If it would be dropped at startup time, return an error. The context should always be available, but
* there's an assert in exec_needs_mount_namespace, so double-check just in case. */
c = unit_get_exec_context(u);
if (!c)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot access unit execution context");
if (path_startswith_strv(dest, c->inaccessible_paths))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s is not accessible to this unit", dest);
/* Ensure that the unit was started in a private mount namespace */
if (!exec_needs_mount_namespace(c, NULL, unit_get_exec_runtime(u)))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit not running in private mount namespace, cannot activate bind mount");
unit_pid = unit_main_pid(u);
if (unit_pid == 0 || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u)))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not running");
propagate_directory = strjoina("/run/systemd/propagate/", u->id);
r = bind_mount_in_namespace(unit_pid,
propagate_directory,
"/run/systemd/incoming/",
src, dest, read_only, make_file_or_directory);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest);
return sd_bus_reply_method_return(message, NULL);
}
const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST),
@ -146,6 +223,16 @@ const sd_bus_vtable bus_service_vtable[] = {
BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecStopPostEx", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
SD_BUS_METHOD_WITH_NAMES("BindMount",
"ssbb",
SD_BUS_PARAM(source)
SD_BUS_PARAM(destination)
SD_BUS_PARAM(read_only)
SD_BUS_PARAM(mkdir),
NULL,,
bus_service_method_bind_mount,
SD_BUS_VTABLE_UNPRIVILEGED),
/* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */
SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),

View File

@ -9,4 +9,5 @@
extern const sd_bus_vtable bus_service_vtable[];
int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitWriteFlags flags, sd_bus_error *error);
int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
int bus_service_commit_properties(Unit *u);

View File

@ -323,38 +323,6 @@ static int property_get_load_error(
return sd_bus_message_append(reply, "(ss)", NULL, NULL);
}
static int bus_verify_manage_units_async_full(
Unit *u,
const char *verb,
int capability,
const char *polkit_message,
bool interactive,
sd_bus_message *call,
sd_bus_error *error) {
const char *details[9] = {
"unit", u->id,
"verb", verb,
};
if (polkit_message) {
details[4] = "polkit.message";
details[5] = polkit_message;
details[6] = "polkit.gettext_domain";
details[7] = GETTEXT_PACKAGE;
}
return bus_verify_polkit_async(
call,
capability,
"org.freedesktop.systemd1.manage-units",
details,
interactive,
UID_INVALID,
&u->manager->polkit_registry,
error);
}
static const char *const polkit_message_for_job[_JOB_TYPE_MAX] = {
[JOB_START] = N_("Authentication is required to start '$(unit)'."),
[JOB_STOP] = N_("Authentication is required to stop '$(unit)'."),

View File

@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "bus-polkit.h"
#include "bus-util.h"
#include "dbus-util.h"
#include "parse-util.h"
@ -153,3 +154,35 @@ int bus_set_transient_usec_internal(
return 1;
}
int bus_verify_manage_units_async_full(
Unit *u,
const char *verb,
int capability,
const char *polkit_message,
bool interactive,
sd_bus_message *call,
sd_bus_error *error) {
const char *details[9] = {
"unit", u->id,
"verb", verb,
};
if (polkit_message) {
details[4] = "polkit.message";
details[5] = polkit_message;
details[6] = "polkit.gettext_domain";
details[7] = GETTEXT_PACKAGE;
}
return bus_verify_polkit_async(
call,
capability,
"org.freedesktop.systemd1.manage-units",
details,
interactive,
UID_INVALID,
&u->manager->polkit_registry,
error);
}

View File

@ -248,3 +248,4 @@ static inline int bus_set_transient_usec(Unit *u, const char *name, usec_t *p, s
static inline int bus_set_transient_usec_fix_0(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, true, message, flags, error);
}
int bus_verify_manage_units_async_full(Unit *u, const char *verb, int capability, const char *polkit_message, bool interactive, sd_bus_message *call, sd_bus_error *error);

View File

@ -679,12 +679,6 @@ static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void
return 0;
}
r = set_ensure_allocated(&m->private_buses, NULL);
if (r < 0) {
log_oom();
return 0;
}
r = sd_bus_new(&bus);
if (r < 0) {
log_warning_errno(r, "Failed to allocate new private connection bus: %m");
@ -752,13 +746,17 @@ static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void
return 0;
}
r = set_put(m->private_buses, bus);
r = set_ensure_put(&m->private_buses, NULL, bus);
if (r == -ENOMEM) {
log_oom();
return 0;
}
if (r < 0) {
log_warning_errno(r, "Failed to add new connection bus to set: %m");
return 0;
}
bus = NULL;
TAKE_PTR(bus);
log_debug("Accepted new private connection.");

View File

@ -1987,13 +1987,12 @@ static int build_pass_environment(const ExecContext *c, char ***ret) {
return 0;
}
static bool exec_needs_mount_namespace(
bool exec_needs_mount_namespace(
const ExecContext *context,
const ExecParameters *params,
const ExecRuntime *runtime) {
assert(context);
assert(params);
if (context->root_image)
return true;
@ -2035,7 +2034,7 @@ static bool exec_needs_mount_namespace(
return true;
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
if (!params->prefix[t])
if (params && !params->prefix[t])
continue;
if (!strv_isempty(context->directories[t].paths))
@ -3115,7 +3114,7 @@ static int apply_mount_namespace(
_cleanup_strv_free_ char **empty_directories = NULL;
const char *tmp_dir = NULL, *var_tmp_dir = NULL;
const char *root_dir = NULL, *root_image = NULL;
_cleanup_free_ char *creds_path = NULL;
_cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL;
NamespaceInfo ns_info;
bool needs_sandboxing;
BindMount *bind_mounts = NULL;
@ -3192,6 +3191,15 @@ static int apply_mount_namespace(
}
}
if (MANAGER_IS_SYSTEM(u->manager)) {
propagate_dir = path_join("/run/systemd/propagate/", u->id);
if (!propagate_dir)
return -ENOMEM;
incoming_dir = strdup("/run/systemd/incoming");
if (!incoming_dir)
return -ENOMEM;
}
r = setup_namespace(root_dir, root_image, context->root_image_options,
&ns_info, context->read_write_paths,
needs_sandboxing ? context->read_only_paths : NULL,
@ -3211,6 +3219,8 @@ static int apply_mount_namespace(
context->root_hash, context->root_hash_size, context->root_hash_path,
context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path,
context->root_verity,
propagate_dir,
incoming_dir,
DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK,
error_path);
@ -6059,15 +6069,11 @@ static int exec_runtime_add(
/* tmp_dir, var_tmp_dir, netns_storage_socket fds are donated on success */
r = hashmap_ensure_allocated(&m->exec_runtime_by_id, &string_hash_ops);
if (r < 0)
return r;
r = exec_runtime_allocate(&rt, id);
if (r < 0)
return r;
r = hashmap_put(m->exec_runtime_by_id, rt->id, rt);
r = hashmap_ensure_put(&m->exec_runtime_by_id, &string_hash_ops, rt->id, rt);
if (r < 0)
return r;

View File

@ -471,3 +471,5 @@ ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
const char* exec_resource_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_resource_type_from_string(const char *s) _pure_;
bool exec_needs_mount_namespace(const ExecContext *context, const ExecParameters *params, const ExecRuntime *runtime);

View File

@ -51,6 +51,7 @@ typedef enum MountMode {
EMPTY_DIR,
SYSFS,
PROCFS,
RUN,
READONLY,
READWRITE,
TMPFS,
@ -76,12 +77,13 @@ typedef struct MountEntry {
LIST_HEAD(MountOptions, image_options);
} MountEntry;
/* If MountAPIVFS= is used, let's mount /sys and /proc into the it, but only as a fallback if the user hasn't mounted
/* If MountAPIVFS= is used, let's mount /sys, /proc, /dev and /run into the it, but only as a fallback if the user hasn't mounted
* something there already. These mounts are hence overridden by any other explicitly configured mounts. */
static const MountEntry apivfs_table[] = {
{ "/proc", PROCFS, false },
{ "/dev", BIND_DEV, false },
{ "/sys", SYSFS, false },
{ "/run", RUN, false, .options_const = "mode=755" TMPFS_LIMITS_RUN, .flags = MS_NOSUID|MS_NODEV|MS_STRICTATIME },
};
/* ProtectKernelTunables= option and the related filesystem APIs */
@ -945,6 +947,20 @@ static int mount_tmpfs(const MountEntry *m) {
return 1;
}
static int mount_run(const MountEntry *m) {
int r;
assert(m);
r = path_is_mount_point(mount_entry_path(m), NULL, 0);
if (r < 0 && r != -ENOENT)
return log_debug_errno(r, "Unable to determine whether /run is already mounted: %m");
if (r > 0) /* make this a NOP if /run is already a mount point */
return 0;
return mount_tmpfs(m);
}
static int mount_images(const MountEntry *m) {
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
_cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
@ -1170,6 +1186,9 @@ static int apply_mount(
case PROCFS:
return mount_procfs(m, ns_info);
case RUN:
return mount_run(m);
case MOUNT_IMAGES:
return mount_images(m);
@ -1282,7 +1301,8 @@ static size_t namespace_calculate_mounts(
const char* tmp_dir,
const char* var_tmp_dir,
const char *creds_path,
const char* log_namespace) {
const char* log_namespace,
bool setup_propagate) {
size_t protect_home_cnt;
size_t protect_system_cnt =
@ -1309,6 +1329,7 @@ static size_t namespace_calculate_mounts(
n_bind_mounts +
n_mount_images +
n_temporary_filesystems +
(setup_propagate ? 1 : 0) + /* /run/systemd/incoming */
ns_info->private_dev +
(ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
(ns_info->protect_kernel_modules ? ELEMENTSOF(protect_kernel_modules_table) : 0) +
@ -1468,6 +1489,8 @@ int setup_namespace(
size_t root_hash_sig_size,
const char *root_hash_sig_path,
const char *verity_data_path,
const char *propagate_dir,
const char *incoming_dir,
DissectImageFlags dissect_image_flags,
char **error_path) {
@ -1476,13 +1499,16 @@ int setup_namespace(
_cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
_cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT;
MountEntry *m = NULL, *mounts = NULL;
bool require_prefix = false;
bool require_prefix = false, setup_propagate = false;
const char *root;
size_t n_mounts;
int r;
assert(ns_info);
if (!isempty(propagate_dir) && !isempty(incoming_dir))
setup_propagate = true;
if (mount_flags == 0)
mount_flags = MS_SHARED;
@ -1566,7 +1592,8 @@ int setup_namespace(
n_mount_images,
tmp_dir, var_tmp_dir,
creds_path,
log_namespace);
log_namespace,
setup_propagate);
if (n_mounts > 0) {
m = mounts = new0(MountEntry, n_mounts);
@ -1735,6 +1762,15 @@ int setup_namespace(
};
}
/* Will be used to add bind mounts at runtime */
if (setup_propagate)
*(m++) = (MountEntry) {
.source_const = propagate_dir,
.path_const = incoming_dir,
.mode = BIND_MOUNT,
.read_only = true,
};
assert(mounts + n_mounts == m);
/* Prepend the root directory where that's necessary */
@ -1759,6 +1795,10 @@ int setup_namespace(
goto finish;
}
/* Create the source directory to allow runtime propagation of mounts */
if (setup_propagate)
(void) mkdir_p(propagate_dir, 0600);
/* Remount / as SLAVE so that nothing now mounted in the namespace
* shows up in the parent */
if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
@ -1900,6 +1940,16 @@ int setup_namespace(
goto finish;
}
/* bind_mount_in_namespace() will MS_MOVE into that directory, and that's only
* supported for non-shared mounts. This needs to happen after remounting / or it will fail. */
if (setup_propagate) {
r = mount(NULL, incoming_dir, NULL, MS_SLAVE, NULL);
if (r < 0) {
log_error_errno(r, "Failed to remount %s with MS_SLAVE: %m", incoming_dir);
goto finish;
}
}
r = 0;
finish:

View File

@ -127,6 +127,8 @@ int setup_namespace(
size_t root_hash_sig_size,
const char *root_hash_sig_path,
const char *root_verity,
const char *propagate_dir,
const char *incoming_dir,
DissectImageFlags dissected_image_flags,
char **error_path);

View File

@ -222,6 +222,10 @@
send_interface="org.freedesktop.systemd1.Manager"
send_member="ReloadOrTryRestartUnit"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="BindMountUnit"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Manager"
send_member="KillUnit"/>
@ -392,6 +396,10 @@
send_interface="org.freedesktop.systemd1.Service"
send_member="AttachProcesses"/>
<allow send_destination="org.freedesktop.systemd1"
send_interface="org.freedesktop.systemd1.Service"
send_member="BindMount"/>
<!-- Managed via polkit or other criteria: org.freedesktop.systemd1.Scope interface -->
<allow send_destination="org.freedesktop.systemd1"

View File

@ -648,11 +648,7 @@ static int transaction_apply(
assert(!j->transaction_prev);
assert(!j->transaction_next);
r = hashmap_ensure_allocated(&m->jobs, NULL);
if (r < 0)
return r;
r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j);
r = hashmap_ensure_put(&m->jobs, NULL, UINT32_TO_PTR(j->id), j);
if (r < 0)
goto rollback;
}

View File

@ -149,10 +149,6 @@ static int transfer_new(Manager *m, Transfer **ret) {
if (hashmap_size(m->transfers) >= TRANSFERS_MAX)
return -E2BIG;
r = hashmap_ensure_allocated(&m->transfers, &trivial_hash_ops);
if (r < 0)
return r;
t = new(Transfer, 1);
if (!t)
return -ENOMEM;
@ -171,7 +167,7 @@ static int transfer_new(Manager *m, Transfer **ret) {
if (asprintf(&t->object_path, "/org/freedesktop/import1/transfer/_%" PRIu32, id) < 0)
return -ENOMEM;
r = hashmap_put(m->transfers, UINT32_TO_PTR(id), t);
r = hashmap_ensure_put(&m->transfers, &trivial_hash_ops, UINT32_TO_PTR(id), t);
if (r < 0)
return r;

View File

@ -102,17 +102,13 @@ static int client_context_compare(const void *a, const void *b) {
}
static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
ClientContext *c;
_cleanup_free_ ClientContext *c = NULL;
int r;
assert(s);
assert(pid_is_valid(pid));
assert(ret);
r = hashmap_ensure_allocated(&s->client_contexts, NULL);
if (r < 0)
return r;
r = prioq_ensure_allocated(&s->client_contexts_lru, client_context_compare);
if (r < 0)
return r;
@ -136,13 +132,11 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
.log_ratelimit_burst = s->ratelimit_burst,
};
r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c);
if (r < 0) {
free(c);
r = hashmap_ensure_put(&s->client_contexts, NULL, PID_TO_PTR(pid), c);
if (r < 0)
return r;
}
*ret = c;
*ret = TAKE_PTR(c);
return 0;
}

View File

@ -234,11 +234,7 @@ int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option *
assert_return(client, -EINVAL);
assert_return(v, -EINVAL);
r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp6_option_hash_ops);
if (r < 0)
return r;
r = ordered_hashmap_put(client->vendor_options, v, v);
r = ordered_hashmap_ensure_put(&client->vendor_options, &dhcp6_option_hash_ops, v, v);
if (r < 0)
return r;
@ -567,11 +563,7 @@ int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
assert_return(client, -EINVAL);
assert_return(v, -EINVAL);
r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp6_option_hash_ops);
if (r < 0)
return r;
r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
r = ordered_hashmap_ensure_put(&client->extra_options, &dhcp6_option_hash_ops, UINT_TO_PTR(v->option), v);
if (r < 0)
return r;

View File

@ -115,19 +115,11 @@ static int lookup_id(sd_netlink *nl, sd_genl_family family, uint16_t *id) {
if (r < 0)
return r;
r = hashmap_ensure_allocated(&nl->genl_family_to_nlmsg_type, NULL);
r = hashmap_ensure_put(&nl->genl_family_to_nlmsg_type, NULL, INT_TO_PTR(family), UINT_TO_PTR(u));
if (r < 0)
return r;
r = hashmap_ensure_allocated(&nl->nlmsg_type_to_genl_family, NULL);
if (r < 0)
return r;
r = hashmap_put(nl->genl_family_to_nlmsg_type, INT_TO_PTR(family), UINT_TO_PTR(u));
if (r < 0)
return r;
r = hashmap_put(nl->nlmsg_type_to_genl_family, UINT_TO_PTR(u), INT_TO_PTR(family));
r = hashmap_ensure_put(&nl->nlmsg_type_to_genl_family, NULL, UINT_TO_PTR(u), INT_TO_PTR(family));
if (r < 0)
return r;

View File

@ -810,17 +810,9 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu
}
int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p;
bool mount_slave_created = false, mount_slave_mounted = false,
mount_tmp_created = false, mount_tmp_mounted = false,
mount_outside_created = false, mount_outside_mounted = false;
_cleanup_free_ char *chased_src = NULL;
int read_only, make_file_or_directory;
const char *dest, *src;
const char *dest, *src, *propagate_directory;
Machine *m = userdata;
struct stat st;
pid_t child;
uid_t uid;
int r;
@ -835,12 +827,12 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
return r;
if (!path_is_absolute(src) || !path_is_normalized(src))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../.");
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized.");
if (isempty(dest))
dest = src;
else if (!path_is_absolute(dest) || !path_is_normalized(dest))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../.");
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized.");
r = bus_verify_polkit_async(
message,
@ -862,205 +854,15 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu
if (uid != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Can't bind mount on container with user namespacing applied.");
/* One day, when bind mounting /proc/self/fd/n works across
* namespace boundaries we should rework this logic to make
* use of it... */
p = strjoina("/run/systemd/nspawn/propagate/", m->name, "/");
if (laccess(p, F_OK) < 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points.");
r = chase_symlinks(src, NULL, CHASE_TRAIL_SLASH, &chased_src, NULL);
propagate_directory = strjoina("/run/systemd/nspawn/propagate/", m->name);
r = bind_mount_in_namespace(m->leader,
propagate_directory,
"/run/host/incoming/",
src, dest, read_only, make_file_or_directory);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to resolve source path: %m");
return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in machine's namespace: %m", src, dest);
if (lstat(chased_src, &st) < 0)
return sd_bus_error_set_errnof(error, errno, "Failed to stat() source path: %m");
if (S_ISLNK(st.st_mode)) /* This shouldn't really happen, given that we just chased the symlinks above, but let's better be safe… */
return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Source directory can't be a symbolic link");
/* Our goal is to install a new bind mount into the container,
possibly read-only. This is irritatingly complex
unfortunately, currently.
First, we start by creating a private playground in /tmp,
that we can mount MS_SLAVE. (Which is necessary, since
MS_MOVE cannot be applied to mounts with MS_SHARED parent
mounts.) */
if (!mkdtemp(mount_slave))
return sd_bus_error_set_errnof(error, errno, "Failed to create playground %s: %m", mount_slave);
mount_slave_created = true;
r = mount_nofollow_verbose(LOG_DEBUG, mount_slave, mount_slave, NULL, MS_BIND, NULL);
if (r < 0) {
sd_bus_error_set_errnof(error, r, "Failed to make bind mount %s: %m", mount_slave);
goto finish;
}
mount_slave_mounted = true;
r = mount_nofollow_verbose(LOG_DEBUG, NULL, mount_slave, NULL, MS_SLAVE, NULL);
if (r < 0) {
sd_bus_error_set_errnof(error, r, "Failed to remount slave %s: %m", mount_slave);
goto finish;
}
/* Second, we mount the source file or directory to a directory inside of our MS_SLAVE playground. */
mount_tmp = strjoina(mount_slave, "/mount");
r = make_mount_point_inode_from_stat(&st, mount_tmp, 0700);
if (r < 0) {
sd_bus_error_set_errnof(error, r, "Failed to create temporary mount point %s: %m", mount_tmp);
goto finish;
}
mount_tmp_created = true;
r = mount_nofollow_verbose(LOG_DEBUG, chased_src, mount_tmp, NULL, MS_BIND, NULL);
if (r < 0) {
sd_bus_error_set_errnof(error, r, "Failed to mount %s: %m", chased_src);
goto finish;
}
mount_tmp_mounted = true;
/* Third, we remount the new bind mount read-only if requested. */
if (read_only) {
r = mount_nofollow_verbose(LOG_DEBUG, NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL);
if (r < 0) {
sd_bus_error_set_errnof(error, r, "Failed to remount read-only %s: %m", mount_tmp);
goto finish;
}
}
/* Fourth, we move the new bind mount into the propagation directory. This way it will appear there read-only
* right-away. */
mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX");
if (S_ISDIR(st.st_mode))
r = mkdtemp(mount_outside) ? 0 : -errno;
else {
r = mkostemp_safe(mount_outside);
safe_close(r);
}
if (r < 0) {
sd_bus_error_set_errnof(error, r, "Cannot create propagation file or directory %s: %m", mount_outside);
goto finish;
}
mount_outside_created = true;
r = mount_nofollow_verbose(LOG_DEBUG, mount_tmp, mount_outside, NULL, MS_MOVE, NULL);
if (r < 0) {
sd_bus_error_set_errnof(error, r, "Failed to move %s to %s: %m", mount_tmp, mount_outside);
goto finish;
}
mount_outside_mounted = true;
mount_tmp_mounted = false;
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_tmp);
else
(void) unlink(mount_tmp);
mount_tmp_created = false;
(void) umount_verbose(LOG_DEBUG, mount_slave, UMOUNT_NOFOLLOW);
mount_slave_mounted = false;
(void) rmdir(mount_slave);
mount_slave_created = false;
if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) {
r = sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
goto finish;
}
r = safe_fork("(sd-bindmnt)", FORK_RESET_SIGNALS, &child);
if (r < 0) {
sd_bus_error_set_errnof(error, r, "Failed to fork(): %m");
goto finish;
}
if (r == 0) {
const char *mount_inside, *q;
int mntfd;
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
q = procfs_file_alloca(m->leader, "ns/mnt");
mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC);
if (mntfd < 0) {
r = log_error_errno(errno, "Failed to open mount namespace of leader: %m");
goto child_fail;
}
if (setns(mntfd, CLONE_NEWNS) < 0) {
r = log_error_errno(errno, "Failed to join namespace of leader: %m");
goto child_fail;
}
if (make_file_or_directory) {
(void) mkdir_parents(dest, 0755);
(void) make_mount_point_inode_from_stat(&st, dest, 0700);
}
mount_inside = strjoina("/run/host/incoming/", basename(mount_outside));
r = mount_nofollow_verbose(LOG_ERR, mount_inside, dest, NULL, MS_MOVE, NULL);
if (r < 0)
goto child_fail;
_exit(EXIT_SUCCESS);
child_fail:
(void) write(errno_pipe_fd[1], &r, sizeof(r));
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
_exit(EXIT_FAILURE);
}
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
r = wait_for_terminate_and_check("(sd-bindmnt)", child, 0);
if (r < 0) {
r = sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m");
goto finish;
}
if (r != EXIT_SUCCESS) {
if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r))
r = sd_bus_error_set_errnof(error, r, "Failed to mount: %m");
else
r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child failed.");
goto finish;
}
r = sd_bus_reply_method_return(message, NULL);
finish:
if (mount_outside_mounted)
(void) umount_verbose(LOG_DEBUG, mount_outside, UMOUNT_NOFOLLOW);
if (mount_outside_created) {
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_outside);
else
(void) unlink(mount_outside);
}
if (mount_tmp_mounted)
(void) umount_verbose(LOG_DEBUG, mount_tmp, UMOUNT_NOFOLLOW);
if (mount_tmp_created) {
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_tmp);
else
(void) unlink(mount_tmp);
}
if (mount_slave_mounted)
(void) umount_verbose(LOG_DEBUG, mount_slave, UMOUNT_NOFOLLOW);
if (mount_slave_created)
(void) rmdir(mount_slave);
return r;
return sd_bus_reply_method_return(message, NULL);
}
int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) {

View File

@ -342,10 +342,6 @@ int config_parse_arp_ip_target_address(
continue;
}
r = ordered_set_ensure_allocated(&b->arp_ip_targets, NULL);
if (r < 0)
return log_oom();
if (ordered_set_size(b->arp_ip_targets) >= NETDEV_BOND_ARP_TARGETS_MAX) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"Too many ARP IP targets are specified. The maximum number is %d. Ignoring assignment: %s",
@ -353,7 +349,9 @@ int config_parse_arp_ip_target_address(
continue;
}
r = ordered_set_put(b->arp_ip_targets, UINT32_TO_PTR(ip.in.s_addr));
r = ordered_set_ensure_put(&b->arp_ip_targets, NULL, UINT32_TO_PTR(ip.in.s_addr));
if (r == -ENOMEM)
return log_oom();
if (r == -EEXIST)
log_syntax(unit, LOG_WARNING, filename, line, r,
"Bond ARP IP target address is duplicated, ignoring assignment: %s", n);

View File

@ -143,11 +143,7 @@ static int route_prefix_new_static(Network *network, const char *filename, unsig
prefix->network = network;
prefix->section = TAKE_PTR(n);
r = hashmap_ensure_allocated(&network->route_prefixes_by_section, &network_config_hash_ops);
if (r < 0)
return r;
r = hashmap_put(network->route_prefixes_by_section, prefix->section, prefix);
r = hashmap_ensure_put(&network->route_prefixes_by_section, &network_config_hash_ops, prefix->section, prefix);
if (r < 0)
return r;

View File

@ -15,14 +15,7 @@ int link_new(Manager *m, Link **ret, int ifindex, const char *ifname) {
assert(m);
assert(ifindex > 0);
r = hashmap_ensure_allocated(&m->links, NULL);
if (r < 0)
return r;
r = hashmap_ensure_allocated(&m->links_by_name, &string_hash_ops);
if (r < 0)
return r;
assert(ifname);
n = strdup(ifname);
if (!n)
@ -39,11 +32,11 @@ int link_new(Manager *m, Link **ret, int ifindex, const char *ifname) {
.required_operstate = LINK_OPERSTATE_RANGE_DEFAULT,
};
r = hashmap_put(m->links_by_name, l->ifname, l);
r = hashmap_ensure_put(&m->links, NULL, INT_TO_PTR(ifindex), l);
if (r < 0)
return r;
r = hashmap_put(m->links, INT_TO_PTR(ifindex), l);
r = hashmap_ensure_put(&m->links_by_name, &string_hash_ops, l->ifname, l);
if (r < 0)
return r;

View File

@ -31,17 +31,15 @@ static int track_pid(Hashmap **h, const char *path, pid_t pid) {
assert(path);
assert(pid_is_valid(pid));
r = hashmap_ensure_allocated(h, NULL);
if (r < 0)
return log_oom();
c = strdup(path);
if (!c)
return log_oom();
r = hashmap_put(*h, PID_TO_PTR(pid), c);
if (r < 0)
r = hashmap_ensure_put(h, NULL, PID_TO_PTR(pid), c);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to store pid " PID_FMT, pid);
TAKE_PTR(c);
return 0;

View File

@ -133,23 +133,33 @@ static int dns_query_candidate_go(DnsQueryCandidate *c) {
DnsTransaction *t;
int r;
unsigned n = 0;
bool notify = false;
assert(c);
c->query->block_ready++;
/* Start the transactions that are not started yet */
SET_FOREACH(t, c->transactions) {
if (t->state != DNS_TRANSACTION_NULL)
continue;
r = dns_transaction_go(t);
if (r < 0)
if (r < 0) {
c->query->block_ready--;
return r;
}
if (r == 0)
/* A transaction is complete. */
notify = true;
n++;
}
c->query->block_ready--;
/* If there was nothing to start, then let's proceed immediately */
if (n == 0)
if (n == 0 || notify)
dns_query_candidate_notify(c);
return 0;

View File

@ -152,8 +152,8 @@ bool dns_transaction_gc(DnsTransaction *t) {
static uint16_t pick_new_id(Manager *m) {
uint16_t new_id;
/* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of
* transactions, and it's much lower than the space of IDs. */
/* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the
* number of transactions, and it's much lower than the space of IDs. */
assert_cc(TRANSACTIONS_MAX < 0xFFFF);
@ -1333,6 +1333,10 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
assert(t);
/* Returns 0 if dns_transaction_complete() has been called. In that case the transaction and query
* candidate objects may have been invalidated and must not be accessed. Returns 1 if the transaction
* has been prepared. */
dns_transaction_stop_timeout(t);
if (!dns_scope_network_good(t->scope)) {
@ -1460,7 +1464,6 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) {
}
static int dns_transaction_make_packet_mdns(DnsTransaction *t) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
bool add_known_answers = false;
DnsTransaction *other;
@ -1628,8 +1631,9 @@ int dns_transaction_go(DnsTransaction *t) {
assert(t);
/* Returns > 0 if the transaction is now pending, returns 0 if could be processed immediately and has finished
* now. */
/* Returns > 0 if the transaction is now pending, returns 0 if could be processed immediately and has
* finished now. In the latter case, the transaction and query candidate objects must not be accessed.
*/
assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
@ -1687,7 +1691,7 @@ int dns_transaction_go(DnsTransaction *t) {
t->state = DNS_TRANSACTION_PENDING;
log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter);
return 0;
return 1;
}
/* Otherwise, we need to ask the network */

View File

@ -27,10 +27,6 @@ int link_new(Manager *m, Link **ret, int ifindex) {
assert(m);
assert(ifindex > 0);
r = hashmap_ensure_allocated(&m->links, NULL);
if (r < 0)
return r;
l = new(Link, 1);
if (!l)
return -ENOMEM;
@ -48,7 +44,7 @@ int link_new(Manager *m, Link **ret, int ifindex) {
if (asprintf(&l->state_file, "/run/systemd/resolve/netif/%i", ifindex) < 0)
return -ENOMEM;
r = hashmap_put(m->links, INT_TO_PTR(ifindex), l);
r = hashmap_ensure_put(&m->links, NULL, INT_TO_PTR(ifindex), l);
if (r < 0)
return r;
@ -56,7 +52,7 @@ int link_new(Manager *m, Link **ret, int ifindex) {
if (ret)
*ret = l;
l = NULL;
TAKE_PTR(l);
return 0;
}

View File

@ -104,11 +104,7 @@ static int add_process(
if (r < 0)
return r;
r = hashmap_ensure_allocated(&cg->pids, &trivial_hash_ops);
if (r < 0)
return r;
return hashmap_put(cg->pids, PID_TO_PTR(pid), (void*) name);
return hashmap_ensure_put(&cg->pids, &trivial_hash_ops, PID_TO_PTR(pid), (void*) name);
}
static void remove_cgroup(Hashmap *cgroups, struct CGroupInfo *cg) {

View File

@ -14,15 +14,19 @@
#include "fs-util.h"
#include "hashmap.h"
#include "libmount-util.h"
#include "mkdir.h"
#include "mount-util.h"
#include "mountpoint-util.h"
#include "namespace-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "set.h"
#include "stat-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
#include "tmpfile-util.h"
int mount_fd(const char *source,
int target_fd,
@ -742,3 +746,225 @@ int mount_option_mangle(
return 0;
}
int bind_mount_in_namespace(
pid_t target,
const char *propagate_path,
const char *incoming_path,
const char *src,
const char *dest,
bool read_only,
bool make_file_or_directory) {
_cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
_cleanup_close_ int self_mntns_fd = -1, mntns_fd = -1, root_fd = -1, pidns_fd = -1, chased_src_fd = -1;
char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p,
chased_src[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
bool mount_slave_created = false, mount_slave_mounted = false,
mount_tmp_created = false, mount_tmp_mounted = false,
mount_outside_created = false, mount_outside_mounted = false;
struct stat st, self_mntns_st;
pid_t child;
int r;
assert(target > 0);
assert(propagate_path);
assert(incoming_path);
assert(src);
assert(dest);
r = namespace_open(target, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd);
if (r < 0)
return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m");
if (fstat(mntns_fd, &st) < 0)
return log_debug_errno(errno, "Failed to fstat mount namespace FD of target process: %m");
r = namespace_open(0, NULL, &self_mntns_fd, NULL, NULL, NULL);
if (r < 0)
return log_debug_errno(r, "Failed to retrieve FDs of systemd's namespace: %m");
if (fstat(self_mntns_fd, &self_mntns_st) < 0)
return log_debug_errno(errno, "Failed to fstat mount namespace FD of systemd: %m");
/* We can't add new mounts at runtime if the process wasn't started in a namespace */
if (st.st_ino == self_mntns_st.st_ino && st.st_dev == self_mntns_st.st_dev)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to activate bind mount in target, not running in a mount namespace");
/* One day, when bind mounting /proc/self/fd/n works across
* namespace boundaries we should rework this logic to make
* use of it... */
p = strjoina(propagate_path, "/");
r = laccess(p, F_OK);
if (r < 0)
return log_debug_errno(r == -ENOENT ? SYNTHETIC_ERRNO(EOPNOTSUPP) : r, "Target does not allow propagation of mount points");
r = chase_symlinks(src, NULL, CHASE_TRAIL_SLASH, NULL, &chased_src_fd);
if (r < 0)
return log_debug_errno(r, "Failed to resolve source path of %s: %m", src);
xsprintf(chased_src, "/proc/self/fd/%i", chased_src_fd);
if (fstat(chased_src_fd, &st) < 0)
return log_debug_errno(errno, "Failed to stat() resolved source path %s: %m", src);
if (S_ISLNK(st.st_mode)) /* This shouldn't really happen, given that we just chased the symlinks above, but let's better be safe… */
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Source directory %s can't be a symbolic link", src);
/* Our goal is to install a new bind mount into the container,
possibly read-only. This is irritatingly complex
unfortunately, currently.
First, we start by creating a private playground in /tmp,
that we can mount MS_SLAVE. (Which is necessary, since
MS_MOVE cannot be applied to mounts with MS_SHARED parent
mounts.) */
if (!mkdtemp(mount_slave))
return log_debug_errno(errno, "Failed to create playground %s: %m", mount_slave);
mount_slave_created = true;
r = mount_nofollow_verbose(LOG_DEBUG, mount_slave, mount_slave, NULL, MS_BIND, NULL);
if (r < 0)
goto finish;
mount_slave_mounted = true;
r = mount_nofollow_verbose(LOG_DEBUG, NULL, mount_slave, NULL, MS_SLAVE, NULL);
if (r < 0)
goto finish;
/* Second, we mount the source file or directory to a directory inside of our MS_SLAVE playground. */
mount_tmp = strjoina(mount_slave, "/mount");
r = make_mount_point_inode_from_stat(&st, mount_tmp, 0700);
if (r < 0) {
log_debug_errno(r, "Failed to create temporary mount point %s: %m", mount_tmp);
goto finish;
}
mount_tmp_created = true;
r = mount_follow_verbose(LOG_DEBUG, chased_src, mount_tmp, NULL, MS_BIND, NULL);
if (r < 0)
goto finish;
mount_tmp_mounted = true;
/* Third, we remount the new bind mount read-only if requested. */
if (read_only) {
r = mount_nofollow_verbose(LOG_DEBUG, NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL);
if (r < 0)
goto finish;
}
/* Fourth, we move the new bind mount into the propagation directory. This way it will appear there read-only
* right-away. */
mount_outside = strjoina(propagate_path, "/XXXXXX");
if (S_ISDIR(st.st_mode))
r = mkdtemp(mount_outside) ? 0 : -errno;
else {
r = mkostemp_safe(mount_outside);
safe_close(r);
}
if (r < 0) {
log_debug_errno(r, "Cannot create propagation file or directory %s: %m", mount_outside);
goto finish;
}
mount_outside_created = true;
r = mount_nofollow_verbose(LOG_DEBUG, mount_tmp, mount_outside, NULL, MS_MOVE, NULL);
if (r < 0)
goto finish;
mount_outside_mounted = true;
mount_tmp_mounted = false;
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_tmp);
else
(void) unlink(mount_tmp);
mount_tmp_created = false;
(void) umount_verbose(LOG_DEBUG, mount_slave, UMOUNT_NOFOLLOW);
mount_slave_mounted = false;
(void) rmdir(mount_slave);
mount_slave_created = false;
if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) {
log_debug_errno(errno, "Failed to create pipe: %m");
goto finish;
}
r = namespace_fork("(sd-bindmnt)", "(sd-bindmnt-inner)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG,
pidns_fd, mntns_fd, -1, -1, root_fd, &child);
if (r < 0)
goto finish;
if (r == 0) {
const char *mount_inside;
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
if (make_file_or_directory) {
(void) mkdir_parents(dest, 0755);
(void) make_mount_point_inode_from_stat(&st, dest, 0700);
}
/* Fifth, move the mount to the right place inside */
mount_inside = strjoina(incoming_path, basename(mount_outside));
r = mount_nofollow_verbose(LOG_ERR, mount_inside, dest, NULL, MS_MOVE, NULL);
if (r < 0)
goto child_fail;
_exit(EXIT_SUCCESS);
child_fail:
(void) write(errno_pipe_fd[1], &r, sizeof(r));
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
_exit(EXIT_FAILURE);
}
errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
r = wait_for_terminate_and_check("(sd-bindmnt)", child, 0);
if (r < 0) {
log_debug_errno(r, "Failed to wait for child: %m");
goto finish;
}
if (r != EXIT_SUCCESS) {
if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r))
log_debug_errno(r, "Failed to mount: %m");
else
log_debug("Child failed.");
goto finish;
}
finish:
if (mount_outside_mounted)
(void) umount_verbose(LOG_DEBUG, mount_outside, UMOUNT_NOFOLLOW);
if (mount_outside_created) {
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_outside);
else
(void) unlink(mount_outside);
}
if (mount_tmp_mounted)
(void) umount_verbose(LOG_DEBUG, mount_tmp, UMOUNT_NOFOLLOW);
if (mount_tmp_created) {
if (S_ISDIR(st.st_mode))
(void) rmdir(mount_tmp);
else
(void) unlink(mount_tmp);
}
if (mount_slave_mounted)
(void) umount_verbose(LOG_DEBUG, mount_slave, UMOUNT_NOFOLLOW);
if (mount_slave_created)
(void) rmdir(mount_slave);
return r;
}

View File

@ -97,3 +97,5 @@ static inline char* umount_and_rmdir_and_free(char *p) {
return mfree(p);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(char*, umount_and_rmdir_and_free);
int bind_mount_in_namespace(pid_t target, const char *propagate_path, const char *incoming_path, const char *src, const char *dest, bool read_only, bool make_file_or_directory);

View File

@ -2391,14 +2391,13 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth
if (startswith(method, "org.varlink.service."))
return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method);
if (hashmap_ensure_allocated(&s->methods, &string_hash_ops) < 0)
return log_oom_debug();
m = strdup(method);
if (!m)
return log_oom_debug();
r = hashmap_put(s->methods, m, callback);
r = hashmap_ensure_put(&s->methods, &string_hash_ops, m, callback);
if (r == -ENOMEM)
return log_oom_debug();
if (r < 0)
return log_debug_errno(r, "Failed to register callback: %m");
if (r > 0)

View File

@ -0,0 +1,41 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#include "bus-error.h"
#include "bus-locator.h"
#include "systemctl-mount.h"
#include "systemctl-util.h"
#include "systemctl.h"
int mount_bind(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *n = NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
polkit_agent_open_maybe();
r = unit_name_mangle(argv[1], arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, &n);
if (r < 0)
return log_error_errno(r, "Failed to mangle unit name: %m");
r = bus_call_method(
bus,
bus_systemd_mgr,
"BindMountUnit",
&error,
NULL,
"sssbb",
n,
argv[2],
argv[3],
arg_read_only,
arg_mkdir);
if (r < 0)
return log_error_errno(r, "Failed to bind mount: %s", bus_error_message(&error, r));
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
int mount_bind(int argc, char *argv[], void *userdata);

View File

@ -38,6 +38,7 @@
#include "systemctl-list-units.h"
#include "systemctl-log-setting.h"
#include "systemctl-logind.h"
#include "systemctl-mount.h"
#include "systemctl-preset-all.h"
#include "systemctl-reset-failed.h"
#include "systemctl-service-watchdogs.h"
@ -105,6 +106,8 @@ bool arg_jobs_before = false;
bool arg_jobs_after = false;
char **arg_clean_what = NULL;
TimestampStyle arg_timestamp_style = TIMESTAMP_PRETTY;
bool arg_read_only = false;
bool arg_mkdir = false;
STATIC_DESTRUCTOR_REGISTER(arg_wall, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
@ -157,6 +160,8 @@ static int systemctl_help(void) {
" freeze PATTERN... Freeze execution of unit processes\n"
" thaw PATTERN... Resume execution of a frozen unit\n"
" set-property UNIT PROPERTY=VALUE... Sets one or more properties of a unit\n"
" bind UNIT PATH [PATH] Bind-mount a path from the host into a\n"
" unit's namespace\n"
" service-log-level SERVICE [LEVEL] Get/set logging threshold for service\n"
" service-log-target SERVICE [TARGET] Get/set logging target for service\n"
" reset-failed [PATTERN...] Reset failed state for all, one, or more\n"
@ -286,6 +291,8 @@ static int systemctl_help(void) {
" 'us': 'Day YYYY-MM-DD HH:MM:SS.UUUUUU TZ\n"
" 'utc': 'Day YYYY-MM-DD HH:MM:SS UTC\n"
" 'us+utc': 'Day YYYY-MM-DD HH:MM:SS.UUUUUU UTC\n"
" --read-only Create read-only bind mount\n"
" --mkdir Create directory before bind-mounting, if missing\n"
"\nSee the %2$s for details.\n"
, program_invocation_short_name
, link
@ -401,6 +408,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_WHAT,
ARG_REBOOT_ARG,
ARG_TIMESTAMP_STYLE,
ARG_READ_ONLY,
ARG_MKDIR,
};
static const struct option options[] = {
@ -457,6 +466,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "what", required_argument, NULL, ARG_WHAT },
{ "reboot-argument", required_argument, NULL, ARG_REBOOT_ARG },
{ "timestamp", required_argument, NULL, ARG_TIMESTAMP_STYLE },
{ "read-only", no_argument, NULL, ARG_READ_ONLY },
{ "mkdir", no_argument, NULL, ARG_MKDIR },
{}
};
@ -878,6 +889,14 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
break;
case ARG_READ_ONLY:
arg_read_only = true;
break;
case ARG_MKDIR:
arg_mkdir = true;
break;
case '.':
/* Output an error mimicking getopt, and print a hint afterwards */
log_error("%s: invalid option -- '.'", program_invocation_name);
@ -1045,6 +1064,7 @@ static int systemctl_main(int argc, char *argv[]) {
{ "add-wants", 3, VERB_ANY, 0, add_dependency },
{ "add-requires", 3, VERB_ANY, 0, add_dependency },
{ "edit", 2, VERB_ANY, VERB_ONLINE_ONLY, edit },
{ "bind", 3, 4, VERB_ONLINE_ONLY, mount_bind },
{}
};

View File

@ -90,3 +90,5 @@ extern bool arg_jobs_before;
extern bool arg_jobs_after;
extern char **arg_clean_what;
extern TimestampStyle arg_timestamp_style;
extern bool arg_read_only;
extern bool arg_mkdir;

View File

@ -1298,10 +1298,6 @@ static int add_implicit(void) {
if (!ordered_hashmap_get(users, *m)) {
_cleanup_(item_freep) Item *j = NULL;
r = ordered_hashmap_ensure_allocated(&users, &item_hash_ops);
if (r < 0)
return log_oom();
j = new0(Item, 1);
if (!j)
return log_oom();
@ -1311,22 +1307,20 @@ static int add_implicit(void) {
if (!j->name)
return log_oom();
r = ordered_hashmap_put(users, j->name, j);
if (r < 0)
r = ordered_hashmap_ensure_put(&users, &item_hash_ops, j->name, j);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to add implicit user '%s': %m", j->name);
log_debug("Adding implicit user '%s' due to m line", j->name);
j = NULL;
TAKE_PTR(j);
}
if (!(ordered_hashmap_get(users, g) ||
ordered_hashmap_get(groups, g))) {
_cleanup_(item_freep) Item *j = NULL;
r = ordered_hashmap_ensure_allocated(&groups, &item_hash_ops);
if (r < 0)
return log_oom();
j = new0(Item, 1);
if (!j)
return log_oom();
@ -1336,12 +1330,14 @@ static int add_implicit(void) {
if (!j->name)
return log_oom();
r = ordered_hashmap_put(groups, j->name, j);
if (r < 0)
r = ordered_hashmap_ensure_put(&groups, &item_hash_ops, j->name, j);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to add implicit group '%s': %m", j->name);
log_debug("Adding implicit group '%s' due to m line", j->name);
j = NULL;
TAKE_PTR(j);
}
}

View File

@ -172,6 +172,8 @@ static void test_protect_kernel_logs(void) {
0,
NULL,
NULL,
NULL,
NULL,
0,
NULL);
assert_se(r == 0);

View File

@ -87,6 +87,8 @@ int main(int argc, char *argv[]) {
0,
NULL,
NULL,
NULL,
NULL,
0,
NULL);
if (r < 0) {

View File

@ -0,0 +1 @@
../TEST-01-BASIC/Makefile

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -e
TEST_DESCRIPTION="test adding new BindPaths while unit is already running"
. $TEST_BASE_DIR/test-functions
do_test "$@" 57

View File

@ -28,6 +28,9 @@ ln -s busybox "$root/bin/cat"
ln -s busybox "$root/bin/tr"
ln -s busybox "$root/bin/ps"
ln -s busybox "$root/bin/ip"
ln -s busybox "$root/bin/seq"
ln -s busybox "$root/bin/sleep"
ln -s busybox "$root/bin/test"
mkdir -p "$root/sbin"
cat <<'EOF' >"$root/sbin/init"

View File

@ -8,6 +8,5 @@ StandardInput=data
StandardInputText=hi
StandardOutput=truncate:/tmp/test-exec-standardoutput-output
StandardError=null
ExecStart=cat
ExecStartPost=cmp /tmp/test-exec-standardoutput-output /tmp/test-exec-standardoutput-expected
ExecStart=sh -c 'cat && cmp /tmp/test-exec-standardoutput-output /tmp/test-exec-standardoutput-expected'
Type=oneshot

View File

@ -629,7 +629,7 @@ create_strace_wrapper() {
cat >$_strace_wrapper <<EOF
#!/usr/bin/env bash
exec strace -D -o /strace.out $ROOTLIBDIR/systemd "\$@"
exec strace -f -D -o /strace.out $ROOTLIBDIR/systemd "\$@"
EOF
chmod 0755 $_strace_wrapper
}
@ -698,14 +698,25 @@ install_missing_libraries() {
# A number of dependencies is now optional via dlopen, so the install
# script will not pick them up, since it looks at linkage.
for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu libfido2; do
if pkg-config --exists ${lib}; then
path=$(pkg-config --variable=libdir ${lib})
if ! [[ ${lib} =~ ^lib ]]; then
lib="lib${lib}"
fi
inst_libs "${path}/${lib}.so"
inst_library "${path}/${lib}.so"
fi
ddebug "Searching for $lib via pkg-config"
if pkg-config --exists ${lib}; then
path=$(pkg-config --variable=libdir ${lib})
if [ -z "${path}" ]; then
ddebug "$lib.pc does not contain a libdir variable, skipping"
continue
fi
if ! [[ ${lib} =~ ^lib ]]; then
lib="lib${lib}"
fi
# Some pkg-config files are broken and give out the wrong paths
# (eg: libcryptsetup), so just ignore them
inst_libs "${path}/${lib}.so" || true
inst_library "${path}/${lib}.so" || true
else
ddebug "$lib.pc not found, skipping"
continue
fi
done
}
@ -864,6 +875,9 @@ check_result_nspawn() {
test -s $TESTDIR/failed && ret=$(($ret+1))
[ -n "$TIMED_OUT" ] && ret=$(($ret+1))
check_asan_reports "$1" || ret=$(($ret+1))
if [ -d "${ARTIFACT_DIRECTORY}" ] && [ -f $1/strace.out ]; then
cp $1/strace.out "${ARTIFACT_DIRECTORY}/"
fi
_umount_dir $initdir
return $ret
}
@ -876,6 +890,9 @@ check_result_qemu() {
[[ -f $initdir/failed ]] && cp -a $initdir/failed $TESTDIR
save_journal $initdir/var/log/journal
check_asan_reports "$initdir" || ret=$(($ret+1))
if [ -d "${ARTIFACT_DIRECTORY}" ] && [ -f $initdir/strace.out ]; then
cp $initdir/strace.out "${ARTIFACT_DIRECTORY}/"
fi
_umount_dir $initdir
[[ -f $TESTDIR/failed ]] && cat $TESTDIR/failed
echo $JOURNAL_LIST

View File

@ -93,6 +93,29 @@ if echo test >> /run/host/os-release; then exit 1; fi
fi
}
function check_machinectl_bind {
local _cmd='for i in $(seq 1 20); do if test -f /tmp/marker; then exit 0; fi; sleep 0.5; done; exit 1;'
cat <<EOF > /run/systemd/system/nspawn_machinectl_bind.service
[Service]
Type=notify
ExecStart=systemd-nspawn $SUSE_OPTS -D /testsuite-13.nc-container --notify-ready=no /bin/sh -x -e -c "$_cmd"
EOF
systemctl start nspawn_machinectl_bind.service
touch /tmp/marker
machinectl bind --mkdir testsuite-13.nc-container /tmp/marker
while systemctl show -P SubState nspawn_machinectl_bind.service | grep -q running
do
sleep 0.1
done
return $(systemctl show -P ExecMainStatus nspawn_machinectl_bind.service)
}
function run {
if [[ "$1" = "yes" && "$is_v2_supported" = "no" ]]; then
printf "Unified cgroup hierarchy is not supported. Skipping.\n" >&2
@ -186,4 +209,6 @@ for api_vfs_writable in yes no network; do
run yes yes $api_vfs_writable
done
check_machinectl_bind
touch /testok

View File

@ -0,0 +1,12 @@
[Service]
RuntimeMaxSec=300
# Adding a new mounts at runtime works if the unit is in the active state,
# so use Type=notify to make sure there's no race condition in the test
Type=notify
RemainAfterExit=yes
MountAPIVFS=yes
PrivateTmp=yes
BindPaths=/run/testservice-57-fixed:/tmp/testfile_fixed
InaccessiblePaths=/run/inaccessible
ExecStartPre=grep -q -F MARKER_FIXED /tmp/testfile_fixed
ExecStart=/bin/sh -c 'systemd-notify --ready; while ! grep -q -F MARKER_RUNTIME /tmp/testfile_runtime; do sleep 0.1; done; test ! -f /run/inaccessible/testfile_fixed'

View File

@ -0,0 +1,5 @@
[Service]
RuntimeMaxSec=10
Type=notify
RemainAfterExit=yes
ExecStart=/bin/sh -c 'systemd-notify --ready; while ! grep -q -F MARKER_RUNTIME /tmp/testfile_runtime; do sleep 0.1; done; exit 0'

View File

@ -0,0 +1,7 @@
[Unit]
Description=TEST-57-RUNTIME-BIND-PATHS
[Service]
ExecStartPre=rm -f /failed /testok
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
Type=oneshot

43
test/units/testsuite-57.sh Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -ex
echo "MARKER_FIXED" > /run/testservice-57-fixed
mkdir -p /run/inaccessible
systemctl start testsuite-57-namespaced.service
# Ensure that inaccessible paths aren't bypassed by the runtime setup
set +e
systemctl bind --mkdir testsuite-57-namespaced.service /run/testservice-57-fixed /run/inaccessible/testfile_fixed && exit 1
set -e
echo "MARKER_RUNTIME" > /run/testservice-57-runtime
systemctl bind --mkdir testsuite-57-namespaced.service /run/testservice-57-runtime /tmp/testfile_runtime
while systemctl show -P SubState testsuite-57-namespaced.service | grep -q running
do
sleep 0.1
done
systemctl is-active testsuite-57-namespaced.service
# Now test that systemctl bind fails when attempted on a non-namespaced unit
systemctl start testsuite-57-non-namespaced.service
set +e
systemctl bind --mkdir testsuite-57-non-namespaced.service /run/testservice-57-runtime /tmp/testfile_runtime && exit 1
set -e
while systemctl show -P SubState testsuite-57-non-namespaced.service | grep -q running
do
sleep 0.1
done
set +e
systemctl is-active testsuite-57-non-namespaced.service && exit 1
set -e
echo OK > /testok
exit 0