diff options
-rw-r--r-- | Makefile.am | 13 | ||||
-rw-r--r-- | NEWS | 14 | ||||
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | man/systemctl.xml | 1 | ||||
-rw-r--r-- | man/systemd-nspawn.xml | 4 | ||||
-rw-r--r-- | man/systemd.nspawn.xml | 6 | ||||
-rw-r--r-- | src/basic/cgroup-util.c | 2 | ||||
-rw-r--r-- | src/nspawn/nspawn-cgroup.c | 162 | ||||
-rw-r--r-- | src/nspawn/nspawn-cgroup.h | 29 | ||||
-rw-r--r-- | src/nspawn/nspawn-expose-ports.c | 277 | ||||
-rw-r--r-- | src/nspawn/nspawn-expose-ports.h | 45 | ||||
-rw-r--r-- | src/nspawn/nspawn-gperf.gperf | 2 | ||||
-rw-r--r-- | src/nspawn/nspawn-mount.c | 869 | ||||
-rw-r--r-- | src/nspawn/nspawn-mount.h (renamed from src/nspawn/nspawn.h) | 23 | ||||
-rw-r--r-- | src/nspawn/nspawn-network.c | 440 | ||||
-rw-r--r-- | src/nspawn/nspawn-network.h | 36 | ||||
-rw-r--r-- | src/nspawn/nspawn-register.c | 244 | ||||
-rw-r--r-- | src/nspawn/nspawn-register.h | 31 | ||||
-rw-r--r-- | src/nspawn/nspawn-settings.c | 1 | ||||
-rw-r--r-- | src/nspawn/nspawn-settings.h | 4 | ||||
-rw-r--r-- | src/nspawn/nspawn-setuid.c | 272 | ||||
-rw-r--r-- | src/nspawn/nspawn-setuid.h | 24 | ||||
-rw-r--r-- | src/nspawn/nspawn.c | 2293 | ||||
-rw-r--r-- | src/systemctl/systemctl.c | 1 |
24 files changed, 2603 insertions, 2194 deletions
diff --git a/Makefile.am b/Makefile.am index 1a844acc13..1dfa0c0677 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2778,9 +2778,20 @@ systemd_cgtop_LDADD = \ # ------------------------------------------------------------------------------ systemd_nspawn_SOURCES = \ src/nspawn/nspawn.c \ - src/nspawn/nspawn.h \ src/nspawn/nspawn-settings.c \ src/nspawn/nspawn-settings.h \ + src/nspawn/nspawn-mount.c \ + src/nspawn/nspawn-mount.h \ + src/nspawn/nspawn-network.c \ + src/nspawn/nspawn-network.h \ + src/nspawn/nspawn-expose-ports.c \ + src/nspawn/nspawn-expose-ports.h \ + src/nspawn/nspawn-cgroup.c \ + src/nspawn/nspawn-cgroup.h \ + src/nspawn/nspawn-register.c \ + src/nspawn/nspawn-register.h \ + src/nspawn/nspawn-setuid.c \ + src/nspawn/nspawn-setuid.h \ src/core/mount-setup.c \ src/core/mount-setup.h \ src/core/loopback-setup.c \ @@ -109,12 +109,14 @@ CHANGES WITH 226: an alternative to configuring container parameters via the nspawn command line. - Contributions from: Cristian Rodríguez, Daniel Mack, David Herrmann, - Eugene Yakubovich, Evgeny Vereshchagin, Filipe Brandenburger, Jan - Alexander Steffens (heftig), Jan Synacek, Kay Sievers, Lennart - Poettering, Mangix, Marcel Holtmann, Martin Pitt, Michal Sekletar, Peter - Hutterer, Piotr Drąg, reverendhomer, Robin Hack, Susant Sahani, Sylvain - Pasche, Thomas Hindoe Paaboel Andersen, Tom Gundersen + Contributions from: Cristian Rodríguez, Daniel Mack, David + Herrmann, Eugene Yakubovich, Evgeny Vereshchagin, Filipe + Brandenburger, Hans de Goede, Jan Alexander Steffens, Jan + Synacek, Kay Sievers, Lennart Poettering, Mangix, Marcel + Holtmann, Martin Pitt, Michael Biebl, Michael Chapman, Michal + Sekletar, Peter Hutterer, Piotr Drąg, reverendhomer, Robin + Hack, Susant Sahani, Sylvain Pasche, Thomas Hindoe Paaboel + Andersen, Tom Gundersen, -- Berlin, 2015-09-XX @@ -26,8 +26,6 @@ External: Features: -* sd-event: somehow handle multiple queued incoming signals - * sd-event: maybe add support for inotify events * PID 1 should send out sd_notify("WATCHDOG=1") messages (for usage in the --user mode, and when run via nspawn) @@ -205,8 +203,6 @@ Features: * "machinectl list-images" should show os-release data, as well as machine-info data (including deployment level) -* nspawn: when start a container "foobar" look for its configuration in a file "foobar.nspawn" in /etc/systemd/nspawn/ as well as next to the actualy directory or image to boot - * Port various tools to make use of verbs.[ch], where applicable * "machinectl history" diff --git a/man/systemctl.xml b/man/systemctl.xml index 20d143741b..37ba4ab6de 100644 --- a/man/systemctl.xml +++ b/man/systemctl.xml @@ -918,6 +918,7 @@ kobject-uevent 1 systemd-udevd-kernel.socket systemd-udevd.service <varname>RequiresOverridable=</varname>, <varname>Requisite=</varname>, <varname>RequisiteOverridable=</varname>, + <varname>ConsistsOf=</varname>, <varname>Wants=</varname>, <varname>BindsTo=</varname> dependencies. If no unit is specified, <filename>default.target</filename> is implied.</para> diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index b1d68b9ecd..bc5dacd98f 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -794,12 +794,12 @@ <filename>/etc/systemd/nspawn/</filename> and <filename>/run/systemd/nspawn/</filename>. If it is found there, its settings are read and used. If it is not found - there it is subequently searched in the same directory as the + there it is subsequently searched in the same directory as the image file or in the immediate parent of the root directory of the container. In this case, if the file is found its settings will be also read and used, but potentially unsafe settings are ignored. Note that in both these cases settings on the - command line take precendence over the corresponding settings + command line take precedence over the corresponding settings from loaded <filename>.nspawn</filename> files, if both are specified. Unsafe settings are considered all settings that elevate the container's privileges or grant access to diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index ac0b911373..7bfafb424f 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -103,7 +103,7 @@ <filename>/etc/systemd/nspawn/</filename> and <filename>/run/systemd/nspawn/</filename>. If found in these directories its settings are read and all of them take full effect - (but are possibly overriden by corresponding command line + (but are possibly overridden by corresponding command line arguments). If not found the file will then be searched next to the image file or in the immediate parent of the root directory of the container. If the file is found there only a subset of the @@ -195,7 +195,7 @@ <citerefentry><refentrytitle>capabilities</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details). The <varname>Capability=</varname> setting specifies additional capabilities to pass on top of the - default set of capabilites. The + default set of capabilities. The <varname>DropCapability=</varname> setting specifies capabilities to drop from the default set. These settings correspond to the <option>--capability=</option> and @@ -280,7 +280,7 @@ <listitem><para>Adds a <literal>tmpfs</literal> mount to the container. Takes a path or a pair of path and option string, - separated by a colon. This option may be used mutiple times to + separated by a colon. This option may be used multiple times to configure multiple <literal>tmpfs</literal> mounts. This option is equivalent to the command line switch <option>--tmpfs=</option>, see diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index a298b29382..f661a54d99 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -2211,7 +2211,7 @@ static const char *cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = { [CGROUP_CONTROLLER_CPUACCT] = "cpuacct", [CGROUP_CONTROLLER_BLKIO] = "blkio", [CGROUP_CONTROLLER_MEMORY] = "memory", - [CGROUP_CONTROLLER_DEVICE] = "device", + [CGROUP_CONTROLLER_DEVICE] = "devices", }; DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController); diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c new file mode 100644 index 0000000000..c0e9ccd7a4 --- /dev/null +++ b/src/nspawn/nspawn-cgroup.c @@ -0,0 +1,162 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/mount.h> + +#include "util.h" +#include "strv.h" +#include "mkdir.h" +#include "fileio.h" +#include "cgroup-util.h" + +#include "nspawn-cgroup.h" + +int chown_cgroup(pid_t pid, uid_t uid_shift) { + _cleanup_free_ char *path = NULL, *fs = NULL; + _cleanup_close_ int fd = -1; + const char *fn; + int r; + + r = cg_pid_get_path(NULL, pid, &path); + if (r < 0) + return log_error_errno(r, "Failed to get container cgroup path: %m"); + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); + if (r < 0) + return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); + + fd = open(fs, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", fs); + + FOREACH_STRING(fn, + ".", + "tasks", + "notify_on_release", + "cgroup.procs", + "cgroup.clone_children", + "cgroup.controllers", + "cgroup.subtree_control", + "cgroup.populated") + if (fchownat(fd, fn, uid_shift, uid_shift, 0) < 0) + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to chown() cgroup file %s, ignoring: %m", fn); + + return 0; +} + +int sync_cgroup(pid_t pid, bool unified_requested) { + _cleanup_free_ char *cgroup = NULL; + char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1]; + bool undo_mount = false; + const char *fn; + int unified, r; + + unified = cg_unified(); + if (unified < 0) + return log_error_errno(unified, "Failed to determine whether the unified hierachy is used: %m"); + + if ((unified > 0) == unified_requested) + return 0; + + /* When the host uses the legacy cgroup setup, but the + * container shall use the unified hierarchy, let's make sure + * we copy the path from the name=systemd hierarchy into the + * unified hierarchy. Similar for the reverse situation. */ + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); + if (r < 0) + return log_error_errno(r, "Failed to get control group of " PID_FMT ": %m", pid); + + /* In order to access the unified hierarchy we need to mount it */ + if (!mkdtemp(tree)) + return log_error_errno(errno, "Failed to generate temporary mount point for unified hierarchy: %m"); + + if (unified) + r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr"); + else + r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "__DEVEL__sane_behavior"); + if (r < 0) { + r = log_error_errno(errno, "Failed to mount unified hierarchy: %m"); + goto finish; + } + + undo_mount = true; + + fn = strjoina(tree, cgroup, "/cgroup.procs"); + (void) mkdir_parents(fn, 0755); + + sprintf(pid_string, PID_FMT, pid); + r = write_string_file(fn, pid_string, 0); + if (r < 0) + log_error_errno(r, "Failed to move process: %m"); + +finish: + if (undo_mount) + (void) umount(tree); + + (void) rmdir(tree); + return r; +} + +int create_subcgroup(pid_t pid, bool unified_requested) { + _cleanup_free_ char *cgroup = NULL; + const char *child; + int unified, r; + CGroupMask supported; + + /* In the unified hierarchy inner nodes may only only contain + * subgroups, but not processes. Hence, if we running in the + * unified hierarchy and the container does the same, and we + * did not create a scope unit for the container move us and + * the container into two separate subcgroups. */ + + if (!unified_requested) + return 0; + + unified = cg_unified(); + if (unified < 0) + return log_error_errno(unified, "Failed to determine whether the unified hierachy is used: %m"); + if (unified == 0) + return 0; + + r = cg_mask_supported(&supported); + if (r < 0) + return log_error_errno(r, "Failed to determine supported controllers: %m"); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); + if (r < 0) + return log_error_errno(r, "Failed to get our control group: %m"); + + child = strjoina(cgroup, "/payload"); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, pid); + if (r < 0) + return log_error_errno(r, "Failed to create %s subcgroup: %m", child); + + child = strjoina(cgroup, "/supervisor"); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, 0); + if (r < 0) + return log_error_errno(r, "Failed to create %s subcgroup: %m", child); + + /* Try to enable as many controllers as possible for the new payload. */ + (void) cg_enable_everywhere(supported, supported, cgroup); + return 0; +} diff --git a/src/nspawn/nspawn-cgroup.h b/src/nspawn/nspawn-cgroup.h new file mode 100644 index 0000000000..985fdfaad5 --- /dev/null +++ b/src/nspawn/nspawn-cgroup.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <stdbool.h> + +int chown_cgroup(pid_t pid, uid_t uid_shift); +int sync_cgroup(pid_t pid, bool unified_requested); +int create_subcgroup(pid_t pid, bool unified_requested); diff --git a/src/nspawn/nspawn-expose-ports.c b/src/nspawn/nspawn-expose-ports.c new file mode 100644 index 0000000000..38250b6e02 --- /dev/null +++ b/src/nspawn/nspawn-expose-ports.c @@ -0,0 +1,277 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-netlink.h" + +#include "util.h" +#include "in-addr-util.h" +#include "firewall-util.h" +#include "local-addresses.h" +#include "netlink-util.h" + +#include "nspawn-expose-ports.h" + +int expose_port_parse(ExposePort **l, const char *s) { + + const char *split, *e; + uint16_t container_port, host_port; + int protocol; + ExposePort *p; + int r; + + assert(l); + assert(s); + + if ((e = startswith(s, "tcp:"))) + protocol = IPPROTO_TCP; + else if ((e = startswith(s, "udp:"))) + protocol = IPPROTO_UDP; + else { + e = s; + protocol = IPPROTO_TCP; + } + + split = strchr(e, ':'); + if (split) { + char v[split - e + 1]; + + memcpy(v, e, split - e); + v[split - e] = 0; + + r = safe_atou16(v, &host_port); + if (r < 0 || host_port <= 0) + return -EINVAL; + + r = safe_atou16(split + 1, &container_port); + } else { + r = safe_atou16(e, &container_port); + host_port = container_port; + } + + if (r < 0 || container_port <= 0) + return -EINVAL; + + LIST_FOREACH(ports, p, *l) + if (p->protocol == protocol && p->host_port == host_port) + return -EEXIST; + + p = new(ExposePort, 1); + if (!p) + return -ENOMEM; + + p->protocol = protocol; + p->host_port = host_port; + p->container_port = container_port; + + LIST_PREPEND(ports, *l, p); + + return 0; +} + +void expose_port_free_all(ExposePort *p) { + + while (p) { + ExposePort *q = p; + LIST_REMOVE(ports, p, q); + free(q); + } +} + +int expose_port_flush(ExposePort* l, union in_addr_union *exposed) { + ExposePort *p; + int r, af = AF_INET; + + assert(exposed); + + if (!l) + return 0; + + if (in_addr_is_null(af, exposed)) + return 0; + + log_debug("Lost IP address."); + + LIST_FOREACH(ports, p, l) { + r = fw_add_local_dnat(false, + af, + p->protocol, + NULL, + NULL, 0, + NULL, 0, + p->host_port, + exposed, + p->container_port, + NULL); + if (r < 0) + log_warning_errno(r, "Failed to modify firewall: %m"); + } + + *exposed = IN_ADDR_NULL; + return 0; +} + +int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed) { + _cleanup_free_ struct local_address *addresses = NULL; + _cleanup_free_ char *pretty = NULL; + union in_addr_union new_exposed; + ExposePort *p; + bool add; + int af = AF_INET, r; + + assert(exposed); + + /* Invoked each time an address is added or removed inside the + * container */ + + if (!l) + return 0; + + r = local_addresses(rtnl, 0, af, &addresses); + if (r < 0) + return log_error_errno(r, "Failed to enumerate local addresses: %m"); + + add = r > 0 && + addresses[0].family == af && + addresses[0].scope < RT_SCOPE_LINK; + + if (!add) + return expose_port_flush(l, exposed); + + new_exposed = addresses[0].address; + if (in_addr_equal(af, exposed, &new_exposed)) + return 0; + + in_addr_to_string(af, &new_exposed, &pretty); + log_debug("New container IP is %s.", strna(pretty)); + + LIST_FOREACH(ports, p, l) { + + r = fw_add_local_dnat(true, + af, + p->protocol, + NULL, + NULL, 0, + NULL, 0, + p->host_port, + &new_exposed, + p->container_port, + in_addr_is_null(af, exposed) ? NULL : exposed); + if (r < 0) + log_warning_errno(r, "Failed to modify firewall: %m"); + } + + *exposed = new_exposed; + return 0; +} + +int expose_port_send_rtnl(int send_fd) { + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + _cleanup_close_ int fd = -1; + ssize_t k; + + assert(send_fd >= 0); + + fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate container netlink: %m"); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + + mh.msg_controllen = cmsg->cmsg_len; + + /* Store away the fd in the socket, so that it stays open as + * long as we run the child */ + k = sendmsg(send_fd, &mh, MSG_NOSIGNAL); + if (k < 0) + return log_error_errno(errno, "Failed to send netlink fd: %m"); + + return 0; +} + +int expose_port_watch_rtnl( + sd_event *event, + int recv_fd, + sd_netlink_message_handler_t handler, + union in_addr_union *exposed, + sd_netlink **ret) { + + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; + int fd, r; + ssize_t k; + + assert(event); + assert(recv_fd >= 0); + assert(ret); + + k = recvmsg(recv_fd, &mh, MSG_NOSIGNAL); + if (k < 0) + return log_error_errno(errno, "Failed to recv netlink fd: %m"); + + cmsg = CMSG_FIRSTHDR(&mh); + assert(cmsg->cmsg_level == SOL_SOCKET); + assert(cmsg->cmsg_type == SCM_RIGHTS); + assert(cmsg->cmsg_len == CMSG_LEN(sizeof(int))); + memcpy(&fd, CMSG_DATA(cmsg), sizeof(int)); + + r = sd_netlink_open_fd(&rtnl, fd); + if (r < 0) { + safe_close(fd); + return log_error_errno(r, "Failed to create rtnl object: %m"); + } + + r = sd_netlink_add_match(rtnl, RTM_NEWADDR, handler, exposed); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m"); + + r = sd_netlink_add_match(rtnl, RTM_DELADDR, handler, exposed); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m"); + + r = sd_netlink_attach_event(rtnl, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to add to even loop: %m"); + + *ret = rtnl; + rtnl = NULL; + + return 0; +} diff --git a/src/nspawn/nspawn-expose-ports.h b/src/nspawn/nspawn-expose-ports.h new file mode 100644 index 0000000000..39cec28695 --- /dev/null +++ b/src/nspawn/nspawn-expose-ports.h @@ -0,0 +1,45 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <inttypes.h> + +#include "sd-event.h" +#include "sd-netlink.h" +#include "list.h" +#include "in-addr-util.h" + +typedef struct ExposePort { + int protocol; + uint16_t host_port; + uint16_t container_port; + LIST_FIELDS(struct ExposePort, ports); +} ExposePort; + +void expose_port_free_all(ExposePort *p); +int expose_port_parse(ExposePort **l, const char *s); + +int expose_port_watch_rtnl(sd_event *event, int recv_fd, sd_netlink_message_handler_t handler, union in_addr_union *exposed, sd_netlink **ret); +int expose_port_send_rtnl(int send_fd); + +int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed); +int expose_port_flush(ExposePort* l, union in_addr_union *exposed); diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 8b68dfa9f5..074b380306 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -1,8 +1,8 @@ %{ #include <stddef.h> #include "conf-parser.h" -#include "nspawn.h" #include "nspawn-settings.h" +#include "nspawn-expose-ports.h" %} struct ConfigPerfItem; %null_strings diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c new file mode 100644 index 0000000000..2bca39f45d --- /dev/null +++ b/src/nspawn/nspawn-mount.c @@ -0,0 +1,869 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/mount.h> + +#include "util.h" +#include "rm-rf.h" +#include "strv.h" +#include "path-util.h" +#include "mkdir.h" +#include "label.h" +#include "set.h" +#include "cgroup-util.h" + +#include "nspawn-mount.h" + +CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t) { + CustomMount *c, *ret; + + assert(l); + assert(n); + assert(t >= 0); + assert(t < _CUSTOM_MOUNT_TYPE_MAX); + + c = realloc(*l, (*n + 1) * sizeof(CustomMount)); + if (!c) + return NULL; + + *l = c; + ret = *l + *n; + (*n)++; + + *ret = (CustomMount) { .type = t }; + + return ret; +} + +void custom_mount_free_all(CustomMount *l, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) { + CustomMount *m = l + i; + + free(m->source); + free(m->destination); + free(m->options); + + if (m->work_dir) { + (void) rm_rf(m->work_dir, REMOVE_ROOT|REMOVE_PHYSICAL); + free(m->work_dir); + } + + strv_free(m->lower); + } + + free(l); +} + +int custom_mount_compare(const void *a, const void *b) { + const CustomMount *x = a, *y = b; + int r; + + r = path_compare(x->destination, y->destination); + if (r != 0) + return r; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + return 0; +} + +int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) { + _cleanup_free_ char *source = NULL, *destination = NULL, *opts = NULL; + const char *p = s; + CustomMount *m; + int r; + + assert(l); + assert(n); + + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + if (r == 1) { + destination = strdup(source); + if (!destination) + return -ENOMEM; + } + + if (r == 2 && !isempty(p)) { + opts = strdup(p); + if (!opts) + return -ENOMEM; + } + + if (!path_is_absolute(source)) + return -EINVAL; + + if (!path_is_absolute(destination)) + return -EINVAL; + + m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND); + if (!m) + return log_oom(); + + m->source = source; + m->destination = destination; + m->read_only = read_only; + m->options = opts; + + source = destination = opts = NULL; + return 0; +} + +int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { + _cleanup_free_ char *path = NULL, *opts = NULL; + const char *p = s; + CustomMount *m; + int r; + + assert(l); + assert(n); + assert(s); + + r = extract_first_word(&p, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + if (isempty(p)) + opts = strdup("mode=0755"); + else + opts = strdup(p); + if (!opts) + return -ENOMEM; + + if (!path_is_absolute(path)) + return -EINVAL; + + m = custom_mount_add(l, n, CUSTOM_MOUNT_TMPFS); + if (!m) + return -ENOMEM; + + m->destination = path; + m->options = opts; + + path = opts = NULL; + return 0; +} + +static int tmpfs_patch_options( + const char *options, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context, + char **ret) { + + char *buf = NULL; + + if (userns && uid_shift != 0) { + assert(uid_shift != UID_INVALID); + + if (options) + (void) asprintf(&buf, "%s,uid=" UID_FMT ",gid=" UID_FMT, options, uid_shift, uid_shift); + else + (void) asprintf(&buf, "uid=" UID_FMT ",gid=" UID_FMT, uid_shift, uid_shift); + if (!buf) + return -ENOMEM; + + options = buf; + } + +#ifdef HAVE_SELINUX + if (selinux_apifs_context) { + char *t; + + if (options) + t = strjoin(options, ",context=\"", selinux_apifs_context, "\"", NULL); + else + t = strjoin("context=\"", selinux_apifs_context, "\"", NULL); + if (!t) { + free(buf); + return -ENOMEM; + } + + free(buf); + buf = t; + } +#endif + + *ret = buf; + return !!buf; +} + +int mount_all(const char *dest, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + typedef struct MountPoint { + const char *what; + const char *where; + const char *type; + const char *options; + unsigned long flags; + bool fatal; + bool userns; + } MountPoint; + + static const MountPoint mount_table[] = { + { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true }, + { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true }, /* Bind mount first */ + { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true }, /* Then, make it r/o */ + { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false }, + { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false }, + { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false }, + { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, false }, +#ifdef HAVE_SELINUX + { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false }, /* Bind mount first */ + { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false }, /* Then, make it r/o */ +#endif + }; + + unsigned k; + int r; + + for (k = 0; k < ELEMENTSOF(mount_table); k++) { + _cleanup_free_ char *where = NULL, *options = NULL; + const char *o; + + if (userns != mount_table[k].userns) + continue; + + where = prefix_root(dest, mount_table[k].where); + if (!where) + return log_oom(); + + r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); + + /* Skip this entry if it is not a remount. */ + if (mount_table[k].what && r > 0) + continue; + + r = mkdir_p(where, 0755); + if (r < 0) { + if (mount_table[k].fatal) + return log_error_errno(r, "Failed to create directory %s: %m", where); + + log_warning_errno(r, "Failed to create directory %s: %m", where); + continue; + } + + o = mount_table[k].options; + if (streq_ptr(mount_table[k].type, "tmpfs")) { + r = tmpfs_patch_options(o, userns, uid_shift, uid_range, selinux_apifs_context, &options); + if (r < 0) + return log_oom(); + if (r > 0) + o = options; + } + + if (mount(mount_table[k].what, + where, + mount_table[k].type, + mount_table[k].flags, + o) < 0) { + + if (mount_table[k].fatal) + return log_error_errno(errno, "mount(%s) failed: %m", where); + + log_warning_errno(errno, "mount(%s) failed, ignoring: %m", where); + } + } + + return 0; +} + +static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) { + const char *p = options; + unsigned long flags = *mount_flags; + char *opts = NULL; + + assert(options); + + for (;;) { + _cleanup_free_ char *word = NULL; + int r = extract_first_word(&p, &word, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to extract mount option: %m"); + if (r == 0) + break; + + if (streq(word, "rbind")) + flags |= MS_REC; + else if (streq(word, "norbind")) + flags &= ~MS_REC; + else { + log_error("Invalid bind mount option: %s", word); + return -EINVAL; + } + } + + *mount_flags = flags; + /* in the future mount_opts will hold string options for mount(2) */ + *mount_opts = opts; + + return 0; +} + +static int mount_bind(const char *dest, CustomMount *m) { + struct stat source_st, dest_st; + const char *where; + unsigned long mount_flags = MS_BIND | MS_REC; + _cleanup_free_ char *mount_opts = NULL; + int r; + + assert(m); + + if (m->options) { + r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts); + if (r < 0) + return r; + } + + if (stat(m->source, &source_st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", m->source); + + where = prefix_roota(dest, m->destination); + + if (stat(where, &dest_st) >= 0) { + if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) { + log_error("Cannot bind mount directory %s on file %s.", m->source, where); + return -EINVAL; + } + + if (!S_ISDIR(source_st.st_mode) && S_ISDIR(dest_st.st_mode)) { + log_error("Cannot bind mount file %s on directory %s.", m->source, where); + return -EINVAL; + } + + } else if (errno == ENOENT) { + r = mkdir_parents_label(where, 0755); + if (r < 0) + return log_error_errno(r, "Failed to make parents of %s: %m", where); + } else { + log_error_errno(errno, "Failed to stat %s: %m", where); + return -errno; + } + + /* Create the mount point. Any non-directory file can be + * mounted on any non-directory file (regular, fifo, socket, + * char, block). + */ + if (S_ISDIR(source_st.st_mode)) + r = mkdir_label(where, 0755); + else + r = touch(where); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to create mount point %s: %m", where); + + if (mount(m->source, where, NULL, mount_flags, mount_opts) < 0) + return log_error_errno(errno, "mount(%s) failed: %m", where); + + if (m->read_only) { + r = bind_remount_recursive(where, true); + if (r < 0) + return log_error_errno(r, "Read-only bind mount failed: %m"); + } + + return 0; +} + +static int mount_tmpfs( + const char *dest, + CustomMount *m, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + const char *where, *options; + _cleanup_free_ char *buf = NULL; + int r; + + assert(dest); + assert(m); + + where = prefix_roota(dest, m->destination); + + r = mkdir_p_label(where, 0755); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where); + + r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + if (r < 0) + return log_oom(); + options = r > 0 ? buf : m->options; + + if (mount("tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "tmpfs mount to %s failed: %m", where); + + return 0; +} + +static char *joined_and_escaped_lower_dirs(char * const *lower) { + _cleanup_strv_free_ char **sv = NULL; + + sv = strv_copy(lower); + if (!sv) + return NULL; + + strv_reverse(sv); + + if (!strv_shell_escape(sv, ",:")) + return NULL; + + return strv_join(sv, ":"); +} + +static int mount_overlay(const char *dest, CustomMount *m) { + _cleanup_free_ char *lower = NULL; + const char *where, *options; + int r; + + assert(dest); + assert(m); + + where = prefix_roota(dest, m->destination); + + r = mkdir_label(where, 0755); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where); + + (void) mkdir_p_label(m->source, 0755); + + lower = joined_and_escaped_lower_dirs(m->lower); + if (!lower) + return log_oom(); + + if (m->read_only) { + _cleanup_free_ char *escaped_source = NULL; + + escaped_source = shell_escape(m->source, ",:"); + if (!escaped_source) + return log_oom(); + + options = strjoina("lowerdir=", escaped_source, ":", lower); + } else { + _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL; + + assert(m->work_dir); + (void) mkdir_label(m->work_dir, 0700); + + escaped_source = shell_escape(m->source, ",:"); + if (!escaped_source) + return log_oom(); + escaped_work_dir = shell_escape(m->work_dir, ",:"); + if (!escaped_work_dir) + return log_oom(); + + options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir); + } + + if (mount("overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options) < 0) + return log_error_errno(errno, "overlay mount to %s failed: %m", where); + + return 0; +} + +int mount_custom( + const char *dest, + CustomMount *mounts, unsigned n, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + unsigned i; + int r; + + assert(dest); + + for (i = 0; i < n; i++) { + CustomMount *m = mounts + i; + + switch (m->type) { + + case CUSTOM_MOUNT_BIND: + r = mount_bind(dest, m); + break; + + case CUSTOM_MOUNT_TMPFS: + r = mount_tmpfs(dest, m, userns, uid_shift, uid_range, selinux_apifs_context); + break; + + case CUSTOM_MOUNT_OVERLAY: + r = mount_overlay(dest, m); + break; + + default: + assert_not_reached("Unknown custom mount type"); + } + + if (r < 0) + return r; + } + + return 0; +} + +static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, bool read_only) { + char *to; + int r; + + to = strjoina(dest, "/sys/fs/cgroup/", hierarchy); + + r = path_is_mount_point(to, 0); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to); + if (r > 0) + return 0; + + mkdir_p(to, 0755); + + /* The superblock mount options of the mount point need to be + * identical to the hosts', and hence writable... */ + if (mount("cgroup", to, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, controller) < 0) + return log_error_errno(errno, "Failed to mount to %s: %m", to); + + /* ... hence let's only make the bind mount read-only, not the + * superblock. */ + if (read_only) { + if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) + return log_error_errno(errno, "Failed to remount %s read-only: %m", to); + } + return 1; +} + +static int mount_legacy_cgroups( + const char *dest, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + _cleanup_set_free_free_ Set *controllers = NULL; + const char *cgroup_root; + int r; + + cgroup_root = prefix_roota(dest, "/sys/fs/cgroup"); + + /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ + r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); + if (r == 0) { + _cleanup_free_ char *options = NULL; + + r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, selinux_apifs_context, &options); + if (r < 0) + return log_oom(); + + if (mount("tmpfs", cgroup_root, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "Failed to mount /sys/fs/cgroup: %m"); + } + + if (cg_unified() > 0) + goto skip_controllers; + + controllers = set_new(&string_hash_ops); + if (!controllers) + return log_oom(); + + r = cg_kernel_controllers(controllers); + if (r < 0) + return log_error_errno(r, "Failed to determine cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ char *controller = NULL, *origin = NULL, *combined = NULL; + + controller = set_steal_first(controllers); + if (!controller) + break; + + origin = prefix_root("/sys/fs/cgroup/", controller); + if (!origin) + return log_oom(); + + r = readlink_malloc(origin, &combined); + if (r == -EINVAL) { + /* Not a symbolic link, but directly a single cgroup hierarchy */ + + r = mount_legacy_cgroup_hierarchy(dest, controller, controller, true); + if (r < 0) + return r; + + } else if (r < 0) + return log_error_errno(r, "Failed to read link %s: %m", origin); + else { + _cleanup_free_ char *target = NULL; + + target = prefix_root(dest, origin); + if (!target) + return log_oom(); + + /* A symbolic link, a combination of controllers in one hierarchy */ + + if (!filename_is_valid(combined)) { + log_warning("Ignoring invalid combined hierarchy %s.", combined); + continue; + } + + r = mount_legacy_cgroup_hierarchy(dest, combined, combined, true); + if (r < 0) + return r; + + r = symlink_idempotent(combined, target); + if (r == -EINVAL) { + log_error("Invalid existing symlink for combined hierarchy"); + return r; + } + if (r < 0) + return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); + } + } + +skip_controllers: + r = mount_legacy_cgroup_hierarchy(dest, "none,name=systemd,xattr", "systemd", false); + if (r < 0) + return r; + + if (mount(NULL, cgroup_root, NULL, MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755") < 0) + return log_error_errno(errno, "Failed to remount %s read-only: %m", cgroup_root); + + return 0; +} + +static int mount_unified_cgroups(const char *dest) { + const char *p; + int r; + + assert(dest); + + p = strjoina(dest, "/sys/fs/cgroup"); + + r = path_is_mount_point(p, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); + if (r > 0) { + p = strjoina(dest, "/sys/fs/cgroup/cgroup.procs"); + if (access(p, F_OK) >= 0) + return 0; + if (errno != ENOENT) + return log_error_errno(errno, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p); + + log_error("%s is already mounted but not a unified cgroup hierarchy. Refusing.", p); + return -EINVAL; + } + + if (mount("cgroup", p, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "__DEVEL__sane_behavior") < 0) + return log_error_errno(errno, "Failed to mount unified cgroup hierarchy to %s: %m", p); + + return 0; +} + +int mount_cgroups( + const char *dest, + bool unified_requested, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + if (unified_requested) + return mount_unified_cgroups(dest); + else + return mount_legacy_cgroups(dest, userns, uid_shift, uid_range, selinux_apifs_context); +} + +int mount_systemd_cgroup_writable( + const char *dest, + bool unified_requested) { + + _cleanup_free_ char *own_cgroup_path = NULL; + const char *systemd_root, *systemd_own; + int r; + + assert(dest); + + r = cg_pid_get_path(NULL, 0, &own_cgroup_path); + if (r < 0) + return log_error_errno(r, "Failed to determine our own cgroup path: %m"); + + /* If we are living in the top-level, then there's nothing to do... */ + if (path_equal(own_cgroup_path, "/")) + return 0; + + if (unified_requested) { + systemd_own = strjoina(dest, "/sys/fs/cgroup", own_cgroup_path); + systemd_root = prefix_roota(dest, "/sys/fs/cgroup"); + } else { + systemd_own = strjoina(dest, "/sys/fs/cgroup/systemd", own_cgroup_path); + systemd_root = prefix_roota(dest, "/sys/fs/cgroup/systemd"); + } + + /* Make our own cgroup a (writable) bind mount */ + if (mount(systemd_own, systemd_own, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to turn %s into a bind mount: %m", own_cgroup_path); + + /* And then remount the systemd cgroup root read-only */ + if (mount(NULL, systemd_root, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) + return log_error_errno(errno, "Failed to mount cgroup root read-only: %m"); + + return 0; +} + +int setup_volatile_state( + const char *directory, + VolatileMode mode, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + _cleanup_free_ char *buf = NULL; + const char *p, *options; + int r; + + assert(directory); + + if (mode != VOLATILE_STATE) + return 0; + + /* --volatile=state means we simply overmount /var + with a tmpfs, and the rest read-only. */ + + r = bind_remount_recursive(directory, true); + if (r < 0) + return log_error_errno(r, "Failed to remount %s read-only: %m", directory); + + p = prefix_roota(directory, "/var"); + r = mkdir(p, 0755); + if (r < 0 && errno != EEXIST) + return log_error_errno(errno, "Failed to create %s: %m", directory); + + options = "mode=755"; + r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + if (r < 0) + return log_oom(); + if (r > 0) + options = buf; + + if (mount("tmpfs", p, "tmpfs", MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "Failed to mount tmpfs to /var: %m"); + + return 0; +} + +int setup_volatile( + const char *directory, + VolatileMode mode, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + bool tmpfs_mounted = false, bind_mounted = false; + char template[] = "/tmp/nspawn-volatile-XXXXXX"; + _cleanup_free_ char *buf = NULL; + const char *f, *t, *options; + int r; + + assert(directory); + + if (mode != VOLATILE_YES) + return 0; + + /* --volatile=yes means we mount a tmpfs to the root dir, and + the original /usr to use inside it, and that read-only. */ + + if (!mkdtemp(template)) + return log_error_errno(errno, "Failed to create temporary directory: %m"); + + options = "mode=755"; + r = tmpfs_patch_options(options, userns, uid_shift, uid_range, selinux_apifs_context, &buf); + if (r < 0) + return log_oom(); + if (r > 0) + options = buf; + + if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME, options) < 0) { + r = log_error_errno(errno, "Failed to mount tmpfs for root directory: %m"); + goto fail; + } + + tmpfs_mounted = true; + + f = prefix_roota(directory, "/usr"); + t = prefix_roota(template, "/usr"); + + r = mkdir(t, 0755); + if (r < 0 && errno != EEXIST) { + r = log_error_errno(errno, "Failed to create %s: %m", t); + goto fail; + } + + if (mount(f, t, NULL, MS_BIND|MS_REC, NULL) < 0) { + r = log_error_errno(errno, "Failed to create /usr bind mount: %m"); + goto fail; + } + + bind_mounted = true; + + r = bind_remount_recursive(t, true); + if (r < 0) { + log_error_errno(r, "Failed to remount %s read-only: %m", t); + goto fail; + } + + if (mount(template, directory, NULL, MS_MOVE, NULL) < 0) { + r = log_error_errno(errno, "Failed to move root mount: %m"); + goto fail; + } + + (void) rmdir(template); + + return 0; + +fail: + if (bind_mounted) + (void) umount(t); + + if (tmpfs_mounted) + (void) umount(template); + (void) rmdir(template); + return r; +} + +VolatileMode volatile_mode_from_string(const char *s) { + int b; + + if (isempty(s)) + return _VOLATILE_MODE_INVALID; + + b = parse_boolean(s); + if (b > 0) + return VOLATILE_YES; + if (b == 0) + return VOLATILE_NO; + + if (streq(s, "state")) + return VOLATILE_STATE; + + return _VOLATILE_MODE_INVALID; +} diff --git a/src/nspawn/nspawn.h b/src/nspawn/nspawn-mount.h index 2124b434fe..5abd44cc4b 100644 --- a/src/nspawn/nspawn.h +++ b/src/nspawn/nspawn-mount.h @@ -21,11 +21,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <inttypes.h> #include <stdbool.h> -#include "list.h" - typedef enum VolatileMode { VOLATILE_NO, VOLATILE_YES, @@ -52,18 +49,22 @@ typedef struct CustomMount { char **lower; } CustomMount; -typedef struct ExposePort { - int protocol; - uint16_t host_port; - uint16_t container_port; - LIST_FIELDS(struct ExposePort, ports); -} ExposePort; +CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t); void custom_mount_free_all(CustomMount *l, unsigned n); int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only); int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s); -void expose_port_free_all(ExposePort *p); -int expose_port_parse(ExposePort **l, const char *s); +int custom_mount_compare(const void *a, const void *b); + +int mount_all(const char *dest, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); + +int mount_cgroups(const char *dest, bool unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); +int mount_systemd_cgroup_writable(const char *dest, bool unified_requested); + +int mount_custom(const char *dest, CustomMount *mounts, unsigned n, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); + +int setup_volatile(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); +int setup_volatile_state(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); VolatileMode volatile_mode_from_string(const char *s); diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c new file mode 100644 index 0000000000..74abe5379a --- /dev/null +++ b/src/nspawn/nspawn-network.c @@ -0,0 +1,440 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <linux/veth.h> +#include <net/if.h> + +#include "sd-id128.h" +#include "sd-netlink.h" +#include "libudev.h" + +#include "util.h" +#include "ether-addr-util.h" +#include "siphash24.h" +#include "netlink-util.h" +#include "udev-util.h" + +#include "nspawn-network.h" + +#define HOST_HASH_KEY SD_ID128_MAKE(1a,37,6f,c7,46,ec,45,0b,ad,a3,d5,31,06,60,5d,b1) +#define CONTAINER_HASH_KEY SD_ID128_MAKE(c3,c4,f9,19,b5,57,b2,1c,e6,cf,14,27,03,9c,ee,a2) +#define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f) + +static int generate_mac( + const char *machine_name, + struct ether_addr *mac, + sd_id128_t hash_key, + uint64_t idx) { + + uint8_t result[8]; + size_t l, sz; + uint8_t *v, *i; + int r; + + l = strlen(machine_name); + sz = sizeof(sd_id128_t) + l; + if (idx > 0) + sz += sizeof(idx); + + v = alloca(sz); + + /* fetch some persistent data unique to the host */ + r = sd_id128_get_machine((sd_id128_t*) v); + if (r < 0) + return r; + + /* combine with some data unique (on this host) to this + * container instance */ + i = mempcpy(v + sizeof(sd_id128_t), machine_name, l); + if (idx > 0) { + idx = htole64(idx); + memcpy(i, &idx, sizeof(idx)); + } + + /* Let's hash the host machine ID plus the container name. We + * use a fixed, but originally randomly created hash key here. */ + siphash24(result, v, sz, hash_key.bytes); + + assert_cc(ETH_ALEN <= sizeof(result)); + memcpy(mac->ether_addr_octet, result, ETH_ALEN); + + /* see eth_random_addr in the kernel */ + mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ + + return 0; +} + +int setup_veth(const char *machine_name, + pid_t pid, + char iface_name[IFNAMSIZ], + bool bridge) { + + _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; + _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; + struct ether_addr mac_host, mac_container; + int r, i; + + /* Use two different interface name prefixes depending whether + * we are in bridge mode or not. */ + snprintf(iface_name, IFNAMSIZ - 1, "%s-%s", + bridge ? "vb" : "ve", machine_name); + + r = generate_mac(machine_name, &mac_container, CONTAINER_HASH_KEY, 0); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for container side: %m"); + + r = generate_mac(machine_name, &mac_host, HOST_HASH_KEY, 0); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for host side: %m"); + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, iface_name); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac_host); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container(m, VETH_INFO_PEER); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, "host0"); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac_container); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new veth interfaces (host0, %s): %m", iface_name); + + i = (int) if_nametoindex(iface_name); + if (i <= 0) + return log_error_errno(errno, "Failed to resolve interface %s: %m", iface_name); + + return i; +} + +int setup_bridge(const char *veth_name, const char *bridge_name) { + _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; + _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; + int r, bridge_ifi; + + assert(veth_name); + assert(bridge_name); + + bridge_ifi = (int) if_nametoindex(bridge_name); + if (bridge_ifi <= 0) + return log_error_errno(errno, "Failed to resolve interface %s: %m", bridge_name); + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP); + if (r < 0) + return log_error_errno(r, "Failed to set IFF_UP flag: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name field: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge_ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink master field: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add veth interface to bridge: %m"); + + return bridge_ifi; +} + +static int parse_interface(struct udev *udev, const char *name) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + char ifi_str[2 + DECIMAL_STR_MAX(int)]; + int ifi; + + ifi = (int) if_nametoindex(name); + if (ifi <= 0) + return log_error_errno(errno, "Failed to resolve interface %s: %m", name); + + sprintf(ifi_str, "n%i", ifi); + d = udev_device_new_from_device_id(udev, ifi_str); + if (!d) + return log_error_errno(errno, "Failed to get udev device for interface %s: %m", name); + + if (udev_device_get_is_initialized(d) <= 0) { + log_error("Network interface %s is not initialized yet.", name); + return -EBUSY; + } + + return ifi; +} + +int move_network_interfaces(pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to append namespace PID to netlink message: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i); + } + + return 0; +} + +int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; + unsigned idx = 0; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; + _cleanup_free_ char *n = NULL; + struct ether_addr mac; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = generate_mac(machine_name, &mac, MACVLAN_HASH_KEY, idx++); + if (r < 0) + return log_error_errno(r, "Failed to create MACVLAN MAC address: %m"); + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface index: %m"); + + n = strappend("mv-", *i); + if (!n) + return log_oom(); + + strshorten(n, IFNAMSIZ-1); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "macvlan"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_MACVLAN_MODE, MACVLAN_MODE_BRIDGE); + if (r < 0) + return log_error_errno(r, "Failed to append macvlan mode: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new macvlan interfaces: %m"); + } + + return 0; +} + +int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; + _cleanup_free_ char *n = NULL; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface index: %m"); + + n = strappend("iv-", *i); + if (!n) + return log_oom(); + + strshorten(n, IFNAMSIZ-1); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "ipvlan"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_IPVLAN_MODE, IPVLAN_MODE_L2); + if (r < 0) + return log_error_errno(r, "Failed to add ipvlan mode: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new ipvlan interfaces: %m"); + } + + return 0; +} diff --git a/src/nspawn/nspawn-network.h b/src/nspawn/nspawn-network.h new file mode 100644 index 0000000000..311e6d06cb --- /dev/null +++ b/src/nspawn/nspawn-network.h @@ -0,0 +1,36 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <net/if.h> + +#include <sys/types.h> +#include <stdbool.h> + +int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge); + +int setup_bridge(const char *veth_name, const char *bridge_name); + +int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces); +int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces); + +int move_network_interfaces(pid_t pid, char **ifaces); diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c new file mode 100644 index 0000000000..b2776a61c2 --- /dev/null +++ b/src/nspawn/nspawn-register.c @@ -0,0 +1,244 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-bus.h" + +#include "util.h" +#include "strv.h" +#include "bus-util.h" +#include "bus-error.h" + +#include "nspawn-register.h" + +int register_machine( + const char *machine_name, + pid_t pid, + const char *directory, + sd_id128_t uuid, + int local_ifindex, + const char *slice, + CustomMount *mounts, + unsigned n_mounts, + int kill_signal, + char **properties, + bool keep_unit) { + + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to open system bus: %m"); + + if (keep_unit) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RegisterMachineWithNetwork", + &error, + NULL, + "sayssusai", + machine_name, + SD_BUS_MESSAGE_APPEND_ID128(uuid), + "nspawn", + "container", + (uint32_t) pid, + strempty(directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); + } else { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + char **i; + unsigned j; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CreateMachineWithNetwork"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sayssusai", + machine_name, + SD_BUS_MESSAGE_APPEND_ID128(uuid), + "nspawn", + "container", + (uint32_t) pid, + strempty(directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(slice)) { + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_append(m, "(sv)", "DevicePolicy", "s", "strict"); + if (r < 0) + return bus_log_create_error(r); + + /* If you make changes here, also make sure to update + * systemd-nspawn@.service, to keep the device + * policies in sync regardless if we are run with or + * without the --keep-unit switch. */ + r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 9, + /* Allow the container to + * access and create the API + * device nodes, so that + * PrivateDevices= in the + * container can work + * fine */ + "/dev/null", "rwm", + "/dev/zero", "rwm", + "/dev/full", "rwm", + "/dev/random", "rwm", + "/dev/urandom", "rwm", + "/dev/tty", "rwm", + "/dev/net/tun", "rwm", + /* Allow the container + * access to ptys. However, + * do not permit the + * container to ever create + * these device nodes. */ + "/dev/pts/ptmx", "rw", + "char-pts", "rw"); + if (r < 0) + return bus_log_create_error(r); + + for (j = 0; j < n_mounts; j++) { + CustomMount *cm = mounts + j; + + if (cm->type != CUSTOM_MOUNT_BIND) + continue; + + r = is_device_node(cm->source); + if (r < 0) + return log_error_errno(r, "Failed to stat %s: %m", cm->source); + + if (r) { + r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 1, + cm->source, cm->read_only ? "r" : "rw"); + if (r < 0) + return log_error_errno(r, "Failed to append message arguments: %m"); + } + } + + if (kill_signal != 0) { + r = sd_bus_message_append(m, "(sv)", "KillSignal", "i", kill_signal); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sv)", "KillMode", "s", "mixed"); + if (r < 0) + return bus_log_create_error(r); + } + + STRV_FOREACH(i, properties) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return bus_log_create_error(r); + + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + } + + if (r < 0) { + log_error("Failed to register machine: %s", bus_error_message(&error, r)); + return r; + } + + return 0; +} + +int terminate_machine(pid_t pid) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; + const char *path; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to open system bus: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineByPID", + &error, + &reply, + "u", + (uint32_t) pid); + if (r < 0) { + /* Note that the machine might already have been + * cleaned up automatically, hence don't consider it a + * failure if we cannot get the machine object. */ + log_debug("Failed to get machine: %s", bus_error_message(&error, r)); + return 0; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + path, + "org.freedesktop.machine1.Machine", + "Terminate", + &error, + NULL, + NULL); + if (r < 0) { + log_debug("Failed to terminate machine: %s", bus_error_message(&error, r)); + return 0; + } + + return 0; +} diff --git a/src/nspawn/nspawn-register.h b/src/nspawn/nspawn-register.h new file mode 100644 index 0000000000..b27841ff59 --- /dev/null +++ b/src/nspawn/nspawn-register.h @@ -0,0 +1,31 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> + +#include "sd-id128.h" + +#include "nspawn-mount.h" + +int register_machine(const char *machine_name, pid_t pid, const char *directory, sd_id128_t uuid, int local_ifindex, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties, bool keep_unit); +int terminate_machine(pid_t pid); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 9a403a3068..419f5d1c40 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -24,7 +24,6 @@ #include "strv.h" #include "cap-list.h" -#include "nspawn.h" #include "nspawn-settings.h" int settings_load(FILE *f, const char *path, Settings **ret) { diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index d9b3b2f7c0..4cec40c1b7 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -24,7 +24,9 @@ #include <stdio.h> #include "macro.h" -#include "nspawn.h" + +#include "nspawn-mount.h" +#include "nspawn-expose-ports.h" typedef enum SettingsMask { SETTING_BOOT = 1 << 0, diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c new file mode 100644 index 0000000000..eda7f62900 --- /dev/null +++ b/src/nspawn/nspawn-setuid.c @@ -0,0 +1,272 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/types.h> +#include <unistd.h> +#include <grp.h> + +#include "util.h" +#include "signal-util.h" +#include "mkdir.h" +#include "process-util.h" + +#include "nspawn-setuid.h" + +static int spawn_getent(const char *database, const char *key, pid_t *rpid) { + int pipe_fds[2]; + pid_t pid; + + assert(database); + assert(key); + assert(rpid); + + if (pipe2(pipe_fds, O_CLOEXEC) < 0) + return log_error_errno(errno, "Failed to allocate pipe: %m"); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork getent child: %m"); + else if (pid == 0) { + int nullfd; + char *empty_env = NULL; + + if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (pipe_fds[0] > 2) + safe_close(pipe_fds[0]); + if (pipe_fds[1] > 2) + safe_close(pipe_fds[1]); + + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) + _exit(EXIT_FAILURE); + + if (dup3(nullfd, STDIN_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (dup3(nullfd, STDERR_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (nullfd > 2) + safe_close(nullfd); + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + close_all_fds(NULL, 0); + + execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env); + execle("/bin/getent", "getent", database, key, NULL, &empty_env); + _exit(EXIT_FAILURE); + } + + pipe_fds[1] = safe_close(pipe_fds[1]); + + *rpid = pid; + + return pipe_fds[0]; +} + +int change_uid_gid(const char *user, char **_home) { + char line[LINE_MAX], *x, *u, *g, *h; + const char *word, *state; + _cleanup_free_ uid_t *uids = NULL; + _cleanup_free_ char *home = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_close_ int fd = -1; + unsigned n_uids = 0; + size_t sz = 0, l; + uid_t uid; + gid_t gid; + pid_t pid; + int r; + + assert(_home); + + if (!user || streq(user, "root") || streq(user, "0")) { + /* Reset everything fully to 0, just in case */ + + r = reset_uid_gid(); + if (r < 0) + return log_error_errno(r, "Failed to become root: %m"); + + *_home = NULL; + return 0; + } + + /* First, get user credentials */ + fd = spawn_getent("passwd", user, &pid); + if (fd < 0) + return fd; + + f = fdopen(fd, "r"); + if (!f) + return log_oom(); + fd = -1; + + if (!fgets(line, sizeof(line), f)) { + + if (!ferror(f)) { + log_error("Failed to resolve user %s.", user); + return -ESRCH; + } + + log_error_errno(errno, "Failed to read from getent: %m"); + return -errno; + } + + truncate_nl(line); + + wait_for_terminate_and_warn("getent passwd", pid, true); + + x = strchr(line, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid user field."); + return -EIO; + } + + u = strchr(x+1, ':'); + if (!u) { + log_error("/etc/passwd entry has invalid password field."); + return -EIO; + } + + u++; + g = strchr(u, ':'); + if (!g) { + log_error("/etc/passwd entry has invalid UID field."); + return -EIO; + } + + *g = 0; + g++; + x = strchr(g, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid GID field."); + return -EIO; + } + + *x = 0; + h = strchr(x+1, ':'); + if (!h) { + log_error("/etc/passwd entry has invalid GECOS field."); + return -EIO; + } + + h++; + x = strchr(h, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid home directory field."); + return -EIO; + } + + *x = 0; + + r = parse_uid(u, &uid); + if (r < 0) { + log_error("Failed to parse UID of user."); + return -EIO; + } + + r = parse_gid(g, &gid); + if (r < 0) { + log_error("Failed to parse GID of user."); + return -EIO; + } + + home = strdup(h); + if (!home) + return log_oom(); + + /* Second, get group memberships */ + fd = spawn_getent("initgroups", user, &pid); + if (fd < 0) + return fd; + + fclose(f); + f = fdopen(fd, "r"); + if (!f) + return log_oom(); + fd = -1; + + if (!fgets(line, sizeof(line), f)) { + if (!ferror(f)) { + log_error("Failed to resolve user %s.", user); + return -ESRCH; + } + + log_error_errno(errno, "Failed to read from getent: %m"); + return -errno; + } + + truncate_nl(line); + + wait_for_terminate_and_warn("getent initgroups", pid, true); + + /* Skip over the username and subsequent separator whitespace */ + x = line; + x += strcspn(x, WHITESPACE); + x += strspn(x, WHITESPACE); + + FOREACH_WORD(word, l, x, state) { + char c[l+1]; + + memcpy(c, word, l); + c[l] = 0; + + if (!GREEDY_REALLOC(uids, sz, n_uids+1)) + return log_oom(); + + r = parse_uid(c, &uids[n_uids++]); + if (r < 0) { + log_error("Failed to parse group data from getent."); + return -EIO; + } + } + + r = mkdir_parents(home, 0775); + if (r < 0) + return log_error_errno(r, "Failed to make home root directory: %m"); + + r = mkdir_safe(home, 0755, uid, gid); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to make home directory: %m"); + + (void) fchown(STDIN_FILENO, uid, gid); + (void) fchown(STDOUT_FILENO, uid, gid); + (void) fchown(STDERR_FILENO, uid, gid); + + if (setgroups(n_uids, uids) < 0) + return log_error_errno(errno, "Failed to set auxiliary groups: %m"); + + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "setregid() failed: %m"); + + if (setresuid(uid, uid, uid) < 0) + return log_error_errno(errno, "setreuid() failed: %m"); + + if (_home) { + *_home = home; + home = NULL; + } + + return 0; +} diff --git a/src/nspawn/nspawn-setuid.h b/src/nspawn/nspawn-setuid.h new file mode 100644 index 0000000000..33be44a946 --- /dev/null +++ b/src/nspawn/nspawn-setuid.h @@ -0,0 +1,24 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +int change_uid_gid(const char *user, char **ret); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index a8afcd1466..33943a4b2f 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -19,91 +19,78 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <signal.h> -#include <sched.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/mount.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> +#ifdef HAVE_BLKID +#include <blkid/blkid.h> +#endif #include <errno.h> -#include <sys/prctl.h> #include <getopt.h> -#include <grp.h> -#include <linux/fs.h> -#include <sys/socket.h> -#include <linux/netlink.h> -#include <net/if.h> -#include <linux/veth.h> -#include <sys/personality.h> #include <linux/loop.h> -#include <sys/file.h> - -#ifdef HAVE_SELINUX -#include <selinux/selinux.h> -#endif - +#include <sched.h> #ifdef HAVE_SECCOMP #include <seccomp.h> #endif - -#ifdef HAVE_BLKID -#include <blkid/blkid.h> +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> #endif +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/file.h> +#include <sys/mount.h> +#include <sys/personality.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <unistd.h> #include "sd-daemon.h" -#include "sd-bus.h" #include "sd-id128.h" -#include "sd-netlink.h" -#include "random-util.h" -#include "log.h" -#include "util.h" -#include "mkdir.h" -#include "rm-rf.h" -#include "macro.h" -#include "missing.h" + +#include "barrier.h" +#include "base-filesystem.h" +#include "blkid-util.h" +#include "btrfs-util.h" +#include "build.h" +#include "cap-list.h" +#include "capability.h" #include "cgroup-util.h" -#include "strv.h" -#include "path-util.h" -#include "loopback-setup.h" +#include "copy.h" #include "dev-setup.h" +#include "env-util.h" +#include "event-util.h" #include "fdset.h" -#include "build.h" #include "fileio.h" -#include "bus-util.h" -#include "bus-error.h" -#include "ptyfwd.h" -#include "env-util.h" -#include "netlink-util.h" -#include "udev-util.h" -#include "blkid-util.h" +#include "formats-util.h" #include "gpt.h" -#include "siphash24.h" -#include "copy.h" -#include "base-filesystem.h" -#include "barrier.h" -#include "event-util.h" -#include "capability.h" -#include "cap-list.h" -#include "btrfs-util.h" +#include "hostname-util.h" +#include "log.h" +#include "loopback-setup.h" #include "machine-image.h" -#include "list.h" -#include "in-addr-util.h" -#include "firewall-util.h" -#include "local-addresses.h" -#include "formats-util.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "netlink-util.h" +#include "path-util.h" #include "process-util.h" -#include "terminal-util.h" -#include "hostname-util.h" -#include "signal-util.h" - +#include "ptyfwd.h" +#include "random-util.h" +#include "rm-rf.h" #ifdef HAVE_SECCOMP #include "seccomp-util.h" #endif +#include "signal-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "udev-util.h" +#include "util.h" -#include "nspawn.h" #include "nspawn-settings.h" +#include "nspawn-mount.h" +#include "nspawn-network.h" +#include "nspawn-expose-ports.h" +#include "nspawn-cgroup.h" +#include "nspawn-register.h" +#include "nspawn-setuid.h" typedef enum ContainerStatus { CONTAINER_TERMINATED, @@ -256,63 +243,6 @@ static void help(void) { , program_invocation_short_name); } -static CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t) { - CustomMount *c, *ret; - - assert(l); - assert(n); - assert(t >= 0); - assert(t < _CUSTOM_MOUNT_TYPE_MAX); - - c = realloc(*l, (*n + 1) * sizeof(CustomMount)); - if (!c) - return NULL; - - *l = c; - ret = *l + *n; - (*n)++; - - *ret = (CustomMount) { .type = t }; - - return ret; -} - -void custom_mount_free_all(CustomMount *l, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) { - CustomMount *m = l + i; - - free(m->source); - free(m->destination); - free(m->options); - - if (m->work_dir) { - (void) rm_rf(m->work_dir, REMOVE_ROOT|REMOVE_PHYSICAL); - free(m->work_dir); - } - - strv_free(m->lower); - } - - free(l); -} - -static int custom_mount_compare(const void *a, const void *b) { - const CustomMount *x = a, *y = b; - int r; - - r = path_compare(x->destination, y->destination); - if (r != 0) - return r; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - return 0; -} static int custom_mounts_prepare(void) { unsigned i; @@ -392,161 +322,6 @@ static int detect_unified_cgroup_hierarchy(void) { return 0; } -VolatileMode volatile_mode_from_string(const char *s) { - int b; - - if (isempty(s)) - return _VOLATILE_MODE_INVALID; - - b = parse_boolean(s); - if (b > 0) - return VOLATILE_YES; - if (b == 0) - return VOLATILE_NO; - - if (streq(s, "state")) - return VOLATILE_STATE; - - return _VOLATILE_MODE_INVALID; -} - -int expose_port_parse(ExposePort **l, const char *s) { - - const char *split, *e; - uint16_t container_port, host_port; - int protocol; - ExposePort *p; - int r; - - if ((e = startswith(s, "tcp:"))) - protocol = IPPROTO_TCP; - else if ((e = startswith(s, "udp:"))) - protocol = IPPROTO_UDP; - else { - e = s; - protocol = IPPROTO_TCP; - } - - split = strchr(e, ':'); - if (split) { - char v[split - e + 1]; - - memcpy(v, e, split - e); - v[split - e] = 0; - - r = safe_atou16(v, &host_port); - if (r < 0 || host_port <= 0) - return -EINVAL; - - r = safe_atou16(split + 1, &container_port); - } else { - r = safe_atou16(e, &container_port); - host_port = container_port; - } - - if (r < 0 || container_port <= 0) - return -EINVAL; - - LIST_FOREACH(ports, p, arg_expose_ports) - if (p->protocol == protocol && p->host_port == host_port) - return -EEXIST; - - p = new(ExposePort, 1); - if (!p) - return -ENOMEM; - - p->protocol = protocol; - p->host_port = host_port; - p->container_port = container_port; - - LIST_PREPEND(ports, *l, p); - - return 0; -} - -int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) { - _cleanup_free_ char *source = NULL, *destination = NULL, *opts = NULL; - const char *p = s; - CustomMount *m; - int r; - - assert(l); - assert(n); - - r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - if (r == 1) { - destination = strdup(source); - if (!destination) - return -ENOMEM; - } - - if (r == 2 && !isempty(p)) { - opts = strdup(p); - if (!opts) - return -ENOMEM; - } - - if (!path_is_absolute(source)) - return -EINVAL; - - if (!path_is_absolute(destination)) - return -EINVAL; - - m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND); - if (!m) - return log_oom(); - - m->source = source; - m->destination = destination; - m->read_only = read_only; - m->options = opts; - - source = destination = opts = NULL; - return 0; -} - -int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { - _cleanup_free_ char *path = NULL, *opts = NULL; - const char *p = s; - CustomMount *m; - int r; - - assert(l); - assert(n); - assert(s); - - r = extract_first_word(&p, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - if (isempty(p)) - opts = strdup("mode=0755"); - else - opts = strdup(p); - if (!opts) - return -ENOMEM; - - if (!path_is_absolute(path)) - return -EINVAL; - - m = custom_mount_add(l, n, CUSTOM_MOUNT_TMPFS); - if (!m) - return -ENOMEM; - - m->destination = path; - m->options = opts; - - path = opts = NULL; - return 0; -} - static int parse_argv(int argc, char *argv[]) { enum { @@ -1170,536 +945,6 @@ static int verify_arguments(void) { return 0; } -static int tmpfs_patch_options(const char *options, char **ret) { - char *buf = NULL; - - if (arg_userns && arg_uid_shift != 0) { - assert(arg_uid_shift != UID_INVALID); - - if (options) - (void) asprintf(&buf, "%s,uid=" UID_FMT ",gid=" UID_FMT, options, arg_uid_shift, arg_uid_shift); - else - (void) asprintf(&buf, "uid=" UID_FMT ",gid=" UID_FMT, arg_uid_shift, arg_uid_shift); - if (!buf) - return -ENOMEM; - - options = buf; - } - -#ifdef HAVE_SELINUX - if (arg_selinux_apifs_context) { - char *t; - - if (options) - t = strjoin(options, ",context=\"", arg_selinux_apifs_context, "\"", NULL); - else - t = strjoin("context=\"", arg_selinux_apifs_context, "\"", NULL); - if (!t) { - free(buf); - return -ENOMEM; - } - - free(buf); - buf = t; - } -#endif - - *ret = buf; - return !!buf; -} - -static int mount_all(const char *dest, bool userns) { - - typedef struct MountPoint { - const char *what; - const char *where; - const char *type; - const char *options; - unsigned long flags; - bool fatal; - bool userns; - } MountPoint; - - static const MountPoint mount_table[] = { - { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, true, true }, - { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, true, true }, /* Bind mount first */ - { NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true }, /* Then, make it r/o */ - { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, true, false }, - { "tmpfs", "/dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, true, false }, - { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false }, - { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, true, false }, - { "tmpfs", "/tmp", "tmpfs", "mode=1777", MS_STRICTATIME, true, false }, -#ifdef HAVE_SELINUX - { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, false, false }, /* Bind mount first */ - { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false }, /* Then, make it r/o */ -#endif - }; - - unsigned k; - int r; - - for (k = 0; k < ELEMENTSOF(mount_table); k++) { - _cleanup_free_ char *where = NULL, *options = NULL; - const char *o; - - if (userns != mount_table[k].userns) - continue; - - where = prefix_root(dest, mount_table[k].where); - if (!where) - return log_oom(); - - r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); - - /* Skip this entry if it is not a remount. */ - if (mount_table[k].what && r > 0) - continue; - - r = mkdir_p(where, 0755); - if (r < 0) { - if (mount_table[k].fatal) - return log_error_errno(r, "Failed to create directory %s: %m", where); - - log_warning_errno(r, "Failed to create directory %s: %m", where); - continue; - } - - o = mount_table[k].options; - if (streq_ptr(mount_table[k].type, "tmpfs")) { - r = tmpfs_patch_options(o, &options); - if (r < 0) - return log_oom(); - if (r > 0) - o = options; - } - - if (mount(mount_table[k].what, - where, - mount_table[k].type, - mount_table[k].flags, - o) < 0) { - - if (mount_table[k].fatal) - return log_error_errno(errno, "mount(%s) failed: %m", where); - - log_warning_errno(errno, "mount(%s) failed, ignoring: %m", where); - } - } - - return 0; -} - -static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) { - const char *p = options; - unsigned long flags = *mount_flags; - char *opts = NULL; - - assert(options); - - for (;;) { - _cleanup_free_ char *word = NULL; - int r = extract_first_word(&p, &word, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to extract mount option: %m"); - if (r == 0) - break; - - if (streq(word, "rbind")) - flags |= MS_REC; - else if (streq(word, "norbind")) - flags &= ~MS_REC; - else { - log_error("Invalid bind mount option: %s", word); - return -EINVAL; - } - } - - *mount_flags = flags; - /* in the future mount_opts will hold string options for mount(2) */ - *mount_opts = opts; - - return 0; -} - -static int mount_bind(const char *dest, CustomMount *m) { - struct stat source_st, dest_st; - const char *where; - unsigned long mount_flags = MS_BIND | MS_REC; - _cleanup_free_ char *mount_opts = NULL; - int r; - - assert(m); - - if (m->options) { - r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts); - if (r < 0) - return r; - } - - if (stat(m->source, &source_st) < 0) - return log_error_errno(errno, "Failed to stat %s: %m", m->source); - - where = prefix_roota(dest, m->destination); - - if (stat(where, &dest_st) >= 0) { - if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) { - log_error("Cannot bind mount directory %s on file %s.", m->source, where); - return -EINVAL; - } - - if (!S_ISDIR(source_st.st_mode) && S_ISDIR(dest_st.st_mode)) { - log_error("Cannot bind mount file %s on directory %s.", m->source, where); - return -EINVAL; - } - - } else if (errno == ENOENT) { - r = mkdir_parents_label(where, 0755); - if (r < 0) - return log_error_errno(r, "Failed to make parents of %s: %m", where); - } else { - log_error_errno(errno, "Failed to stat %s: %m", where); - return -errno; - } - - /* Create the mount point. Any non-directory file can be - * mounted on any non-directory file (regular, fifo, socket, - * char, block). - */ - if (S_ISDIR(source_st.st_mode)) - r = mkdir_label(where, 0755); - else - r = touch(where); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Failed to create mount point %s: %m", where); - - if (mount(m->source, where, NULL, mount_flags, mount_opts) < 0) - return log_error_errno(errno, "mount(%s) failed: %m", where); - - if (m->read_only) { - r = bind_remount_recursive(where, true); - if (r < 0) - return log_error_errno(r, "Read-only bind mount failed: %m"); - } - - return 0; -} - -static int mount_tmpfs(const char *dest, CustomMount *m) { - const char *where, *options; - _cleanup_free_ char *buf = NULL; - int r; - - assert(dest); - assert(m); - - where = prefix_roota(dest, m->destination); - - r = mkdir_p_label(where, 0755); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where); - - r = tmpfs_patch_options(m->options, &buf); - if (r < 0) - return log_oom(); - options = r > 0 ? buf : m->options; - - if (mount("tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "tmpfs mount to %s failed: %m", where); - - return 0; -} - -static char *joined_and_escaped_lower_dirs(char * const *lower) { - _cleanup_strv_free_ char **sv = NULL; - - sv = strv_copy(lower); - if (!sv) - return NULL; - - strv_reverse(sv); - - if (!strv_shell_escape(sv, ",:")) - return NULL; - - return strv_join(sv, ":"); -} - -static int mount_overlay(const char *dest, CustomMount *m) { - _cleanup_free_ char *lower = NULL; - const char *where, *options; - int r; - - assert(dest); - assert(m); - - where = prefix_roota(dest, m->destination); - - r = mkdir_label(where, 0755); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where); - - (void) mkdir_p_label(m->source, 0755); - - lower = joined_and_escaped_lower_dirs(m->lower); - if (!lower) - return log_oom(); - - if (m->read_only) { - _cleanup_free_ char *escaped_source = NULL; - - escaped_source = shell_escape(m->source, ",:"); - if (!escaped_source) - return log_oom(); - - options = strjoina("lowerdir=", escaped_source, ":", lower); - } else { - _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL; - - assert(m->work_dir); - (void) mkdir_label(m->work_dir, 0700); - - escaped_source = shell_escape(m->source, ",:"); - if (!escaped_source) - return log_oom(); - escaped_work_dir = shell_escape(m->work_dir, ",:"); - if (!escaped_work_dir) - return log_oom(); - - options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir); - } - - if (mount("overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options) < 0) - return log_error_errno(errno, "overlay mount to %s failed: %m", where); - - return 0; -} - -static int mount_custom(const char *dest) { - unsigned i; - int r; - - assert(dest); - - for (i = 0; i < arg_n_custom_mounts; i++) { - CustomMount *m = &arg_custom_mounts[i]; - - switch (m->type) { - - case CUSTOM_MOUNT_BIND: - r = mount_bind(dest, m); - break; - - case CUSTOM_MOUNT_TMPFS: - r = mount_tmpfs(dest, m); - break; - - case CUSTOM_MOUNT_OVERLAY: - r = mount_overlay(dest, m); - break; - - default: - assert_not_reached("Unknown custom mount type"); - } - - if (r < 0) - return r; - } - - return 0; -} - -static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, bool read_only) { - char *to; - int r; - - to = strjoina(dest, "/sys/fs/cgroup/", hierarchy); - - r = path_is_mount_point(to, 0); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to); - if (r > 0) - return 0; - - mkdir_p(to, 0755); - - /* The superblock mount options of the mount point need to be - * identical to the hosts', and hence writable... */ - if (mount("cgroup", to, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, controller) < 0) - return log_error_errno(errno, "Failed to mount to %s: %m", to); - - /* ... hence let's only make the bind mount read-only, not the - * superblock. */ - if (read_only) { - if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to remount %s read-only: %m", to); - } - return 1; -} - -static int mount_legacy_cgroups(const char *dest) { - _cleanup_set_free_free_ Set *controllers = NULL; - const char *cgroup_root; - int r; - - cgroup_root = prefix_roota(dest, "/sys/fs/cgroup"); - - /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ - r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW); - if (r < 0) - return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); - if (r == 0) { - _cleanup_free_ char *options = NULL; - - r = tmpfs_patch_options("mode=755", &options); - if (r < 0) - return log_oom(); - - if (mount("tmpfs", cgroup_root, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "Failed to mount /sys/fs/cgroup: %m"); - } - - if (cg_unified() > 0) - goto skip_controllers; - - controllers = set_new(&string_hash_ops); - if (!controllers) - return log_oom(); - - r = cg_kernel_controllers(controllers); - if (r < 0) - return log_error_errno(r, "Failed to determine cgroup controllers: %m"); - - for (;;) { - _cleanup_free_ char *controller = NULL, *origin = NULL, *combined = NULL; - - controller = set_steal_first(controllers); - if (!controller) - break; - - origin = prefix_root("/sys/fs/cgroup/", controller); - if (!origin) - return log_oom(); - - r = readlink_malloc(origin, &combined); - if (r == -EINVAL) { - /* Not a symbolic link, but directly a single cgroup hierarchy */ - - r = mount_legacy_cgroup_hierarchy(dest, controller, controller, true); - if (r < 0) - return r; - - } else if (r < 0) - return log_error_errno(r, "Failed to read link %s: %m", origin); - else { - _cleanup_free_ char *target = NULL; - - target = prefix_root(dest, origin); - if (!target) - return log_oom(); - - /* A symbolic link, a combination of controllers in one hierarchy */ - - if (!filename_is_valid(combined)) { - log_warning("Ignoring invalid combined hierarchy %s.", combined); - continue; - } - - r = mount_legacy_cgroup_hierarchy(dest, combined, combined, true); - if (r < 0) - return r; - - r = symlink_idempotent(combined, target); - if (r == -EINVAL) { - log_error("Invalid existing symlink for combined hierarchy"); - return r; - } - if (r < 0) - return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); - } - } - -skip_controllers: - r = mount_legacy_cgroup_hierarchy(dest, "none,name=systemd,xattr", "systemd", false); - if (r < 0) - return r; - - if (mount(NULL, cgroup_root, NULL, MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755") < 0) - return log_error_errno(errno, "Failed to remount %s read-only: %m", cgroup_root); - - return 0; -} - -static int mount_unified_cgroups(const char *dest) { - const char *p; - int r; - - assert(dest); - - p = strjoina(dest, "/sys/fs/cgroup"); - - r = path_is_mount_point(p, AT_SYMLINK_FOLLOW); - if (r < 0) - return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); - if (r > 0) { - p = strjoina(dest, "/sys/fs/cgroup/cgroup.procs"); - if (access(p, F_OK) >= 0) - return 0; - if (errno != ENOENT) - return log_error_errno(errno, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p); - - log_error("%s is already mounted but not a unified cgroup hierarchy. Refusing.", p); - return -EINVAL; - } - - if (mount("cgroup", p, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "__DEVEL__sane_behavior") < 0) - return log_error_errno(errno, "Failed to mount unified cgroup hierarchy to %s: %m", p); - - return 0; -} - -static int mount_cgroups(const char *dest) { - if (arg_unified_cgroup_hierarchy) - return mount_unified_cgroups(dest); - else - return mount_legacy_cgroups(dest); -} - -static int mount_systemd_cgroup_writable(const char *dest) { - _cleanup_free_ char *own_cgroup_path = NULL; - const char *systemd_root, *systemd_own; - int r; - - assert(dest); - - r = cg_pid_get_path(NULL, 0, &own_cgroup_path); - if (r < 0) - return log_error_errno(r, "Failed to determine our own cgroup path: %m"); - - /* If we are living in the top-level, then there's nothing to do... */ - if (path_equal(own_cgroup_path, "/")) - return 0; - - if (arg_unified_cgroup_hierarchy) { - systemd_own = strjoina(dest, "/sys/fs/cgroup", own_cgroup_path); - systemd_root = prefix_roota(dest, "/sys/fs/cgroup"); - } else { - systemd_own = strjoina(dest, "/sys/fs/cgroup/systemd", own_cgroup_path); - systemd_root = prefix_roota(dest, "/sys/fs/cgroup/systemd"); - } - - /* Make our own cgroup a (writable) bind mount */ - if (mount(systemd_own, systemd_own, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to turn %s into a bind mount: %m", own_cgroup_path); - - /* And then remount the systemd cgroup root read-only */ - if (mount(NULL, systemd_root, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to mount cgroup root read-only: %m"); - - return 0; -} - static int userns_lchown(const char *p, uid_t uid, gid_t gid) { assert(p); @@ -1837,114 +1082,6 @@ static int setup_resolv_conf(const char *dest) { return 0; } -static int setup_volatile_state(const char *directory) { - _cleanup_free_ char *buf = NULL; - const char *p, *options; - int r; - - assert(directory); - - if (arg_volatile_mode != VOLATILE_STATE) - return 0; - - /* --volatile=state means we simply overmount /var - with a tmpfs, and the rest read-only. */ - - r = bind_remount_recursive(directory, true); - if (r < 0) - return log_error_errno(r, "Failed to remount %s read-only: %m", directory); - - p = prefix_roota(directory, "/var"); - r = mkdir(p, 0755); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create %s: %m", directory); - - options = "mode=755"; - r = tmpfs_patch_options(options, &buf); - if (r < 0) - return log_oom(); - if (r > 0) - options = buf; - - if (mount("tmpfs", p, "tmpfs", MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "Failed to mount tmpfs to /var: %m"); - - return 0; -} - -static int setup_volatile(const char *directory) { - bool tmpfs_mounted = false, bind_mounted = false; - char template[] = "/tmp/nspawn-volatile-XXXXXX"; - _cleanup_free_ char *buf = NULL; - const char *f, *t, *options; - int r; - - assert(directory); - - if (arg_volatile_mode != VOLATILE_YES) - return 0; - - /* --volatile=yes means we mount a tmpfs to the root dir, and - the original /usr to use inside it, and that read-only. */ - - if (!mkdtemp(template)) - return log_error_errno(errno, "Failed to create temporary directory: %m"); - - options = "mode=755"; - r = tmpfs_patch_options(options, &buf); - if (r < 0) - return log_oom(); - if (r > 0) - options = buf; - - if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME, options) < 0) { - r = log_error_errno(errno, "Failed to mount tmpfs for root directory: %m"); - goto fail; - } - - tmpfs_mounted = true; - - f = prefix_roota(directory, "/usr"); - t = prefix_roota(template, "/usr"); - - r = mkdir(t, 0755); - if (r < 0 && errno != EEXIST) { - r = log_error_errno(errno, "Failed to create %s: %m", t); - goto fail; - } - - if (mount(f, t, NULL, MS_BIND|MS_REC, NULL) < 0) { - r = log_error_errno(errno, "Failed to create /usr bind mount: %m"); - goto fail; - } - - bind_mounted = true; - - r = bind_remount_recursive(t, true); - if (r < 0) { - log_error_errno(r, "Failed to remount %s read-only: %m", t); - goto fail; - } - - if (mount(template, directory, NULL, MS_MOVE, NULL) < 0) { - r = log_error_errno(errno, "Failed to move root mount: %m"); - goto fail; - } - - (void) rmdir(template); - - return 0; - -fail: - if (bind_mounted) - (void) umount(t); - - if (tmpfs_mounted) - (void) umount(template); - (void) rmdir(template); - return r; -} - static char* id128_format_as_uuid(sd_id128_t id, char s[37]) { assert(s); @@ -2183,141 +1320,6 @@ static int setup_kmsg(const char *dest, int kmsg_socket) { return 0; } -static int send_rtnl(int send_fd) { - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - struct msghdr mh = { - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg; - _cleanup_close_ int fd = -1; - ssize_t k; - - assert(send_fd >= 0); - - if (!arg_expose_ports) - return 0; - - fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE); - if (fd < 0) - return log_error_errno(errno, "Failed to allocate container netlink: %m"); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); - - mh.msg_controllen = cmsg->cmsg_len; - - /* Store away the fd in the socket, so that it stays open as - * long as we run the child */ - k = sendmsg(send_fd, &mh, MSG_NOSIGNAL); - if (k < 0) - return log_error_errno(errno, "Failed to send netlink fd: %m"); - - return 0; -} - -static int flush_ports(union in_addr_union *exposed) { - ExposePort *p; - int r, af = AF_INET; - - assert(exposed); - - if (!arg_expose_ports) - return 0; - - if (in_addr_is_null(af, exposed)) - return 0; - - log_debug("Lost IP address."); - - LIST_FOREACH(ports, p, arg_expose_ports) { - r = fw_add_local_dnat(false, - af, - p->protocol, - NULL, - NULL, 0, - NULL, 0, - p->host_port, - exposed, - p->container_port, - NULL); - if (r < 0) - log_warning_errno(r, "Failed to modify firewall: %m"); - } - - *exposed = IN_ADDR_NULL; - return 0; -} - -static int expose_ports(sd_netlink *rtnl, union in_addr_union *exposed) { - _cleanup_free_ struct local_address *addresses = NULL; - _cleanup_free_ char *pretty = NULL; - union in_addr_union new_exposed; - ExposePort *p; - bool add; - int af = AF_INET, r; - - assert(exposed); - - /* Invoked each time an address is added or removed inside the - * container */ - - if (!arg_expose_ports) - return 0; - - r = local_addresses(rtnl, 0, af, &addresses); - if (r < 0) - return log_error_errno(r, "Failed to enumerate local addresses: %m"); - - add = r > 0 && - addresses[0].family == af && - addresses[0].scope < RT_SCOPE_LINK; - - if (!add) - return flush_ports(exposed); - - new_exposed = addresses[0].address; - if (in_addr_equal(af, exposed, &new_exposed)) - return 0; - - in_addr_to_string(af, &new_exposed, &pretty); - log_debug("New container IP is %s.", strna(pretty)); - - LIST_FOREACH(ports, p, arg_expose_ports) { - - r = fw_add_local_dnat(true, - af, - p->protocol, - NULL, - NULL, 0, - NULL, 0, - p->host_port, - &new_exposed, - p->container_port, - in_addr_is_null(af, exposed) ? NULL : exposed); - if (r < 0) - log_warning_errno(r, "Failed to modify firewall: %m"); - } - - *exposed = new_exposed; - return 0; -} - -void expose_port_free_all(ExposePort *p) { - - while (p) { - ExposePort *q = p; - LIST_REMOVE(ports, p, q); - free(q); - } -} - static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { union in_addr_union *exposed = userdata; @@ -2325,62 +1327,7 @@ static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *user assert(m); assert(exposed); - expose_ports(rtnl, exposed); - return 0; -} - -static int watch_rtnl(sd_event *event, int recv_fd, union in_addr_union *exposed, sd_netlink **ret) { - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - struct msghdr mh = { - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg; - _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; - int fd, r; - ssize_t k; - - assert(event); - assert(recv_fd >= 0); - assert(ret); - - if (!arg_expose_ports) - return 0; - - k = recvmsg(recv_fd, &mh, MSG_NOSIGNAL); - if (k < 0) - return log_error_errno(errno, "Failed to recv netlink fd: %m"); - - cmsg = CMSG_FIRSTHDR(&mh); - assert(cmsg->cmsg_level == SOL_SOCKET); - assert(cmsg->cmsg_type == SCM_RIGHTS); - assert(cmsg->cmsg_len == CMSG_LEN(sizeof(int))); - memcpy(&fd, CMSG_DATA(cmsg), sizeof(int)); - - r = sd_netlink_open_fd(&rtnl, fd); - if (r < 0) { - safe_close(fd); - return log_error_errno(r, "Failed to create rtnl object: %m"); - } - - r = sd_netlink_add_match(rtnl, RTM_NEWADDR, on_address_change, exposed); - if (r < 0) - return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m"); - - r = sd_netlink_add_match(rtnl, RTM_DELADDR, on_address_change, exposed); - if (r < 0) - return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m"); - - r = sd_netlink_attach_event(rtnl, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to add to even loop: %m"); - - *ret = rtnl; - rtnl = NULL; - + expose_port_execute(rtnl, arg_expose_ports, exposed); return 0; } @@ -2557,220 +1504,6 @@ static int drop_capabilities(void) { return capability_bounding_set_drop(~arg_retain, false); } -static int register_machine(pid_t pid, int local_ifindex) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; - int r; - - if (!arg_register) - return 0; - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); - - if (arg_keep_unit) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RegisterMachineWithNetwork", - &error, - NULL, - "sayssusai", - arg_machine, - SD_BUS_MESSAGE_APPEND_ID128(arg_uuid), - "nspawn", - "container", - (uint32_t) pid, - strempty(arg_directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); - } else { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - char **i; - unsigned j; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "CreateMachineWithNetwork"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sayssusai", - arg_machine, - SD_BUS_MESSAGE_APPEND_ID128(arg_uuid), - "nspawn", - "container", - (uint32_t) pid, - strempty(arg_directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - if (!isempty(arg_slice)) { - r = sd_bus_message_append(m, "(sv)", "Slice", "s", arg_slice); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_append(m, "(sv)", "DevicePolicy", "s", "strict"); - if (r < 0) - return bus_log_create_error(r); - - /* If you make changes here, also make sure to update - * systemd-nspawn@.service, to keep the device - * policies in sync regardless if we are run with or - * without the --keep-unit switch. */ - r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 9, - /* Allow the container to - * access and create the API - * device nodes, so that - * PrivateDevices= in the - * container can work - * fine */ - "/dev/null", "rwm", - "/dev/zero", "rwm", - "/dev/full", "rwm", - "/dev/random", "rwm", - "/dev/urandom", "rwm", - "/dev/tty", "rwm", - "/dev/net/tun", "rwm", - /* Allow the container - * access to ptys. However, - * do not permit the - * container to ever create - * these device nodes. */ - "/dev/pts/ptmx", "rw", - "char-pts", "rw"); - if (r < 0) - return bus_log_create_error(r); - - for (j = 0; j < arg_n_custom_mounts; j++) { - CustomMount *cm = &arg_custom_mounts[j]; - - if (cm->type != CUSTOM_MOUNT_BIND) - continue; - - r = is_device_node(cm->source); - if (r < 0) - return log_error_errno(r, "Failed to stat %s: %m", cm->source); - - if (r) { - r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 1, - cm->source, cm->read_only ? "r" : "rw"); - if (r < 0) - return log_error_errno(r, "Failed to append message arguments: %m"); - } - } - - if (arg_kill_signal != 0) { - r = sd_bus_message_append(m, "(sv)", "KillSignal", "i", arg_kill_signal); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "(sv)", "KillMode", "s", "mixed"); - if (r < 0) - return bus_log_create_error(r); - } - - STRV_FOREACH(i, arg_property) { - r = sd_bus_message_open_container(m, 'r', "sv"); - if (r < 0) - return bus_log_create_error(r); - - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - } - - if (r < 0) { - log_error("Failed to register machine: %s", bus_error_message(&error, r)); - return r; - } - - return 0; -} - -static int terminate_machine(pid_t pid) { - _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; - _cleanup_bus_flush_close_unref_ sd_bus *bus = NULL; - const char *path; - int r; - - if (!arg_register) - return 0; - - /* If we are reusing the unit, then just exit, systemd will do - * the right thing when we exit. */ - if (arg_keep_unit) - return 0; - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineByPID", - &error, - &reply, - "u", - (uint32_t) pid); - if (r < 0) { - /* Note that the machine might already have been - * cleaned up automatically, hence don't consider it a - * failure if we cannot get the machine object. */ - log_debug("Failed to get machine: %s", bus_error_message(&error, r)); - return 0; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - path, - "org.freedesktop.machine1.Machine", - "Terminate", - &error, - NULL, - NULL); - if (r < 0) { - log_debug("Failed to terminate machine: %s", bus_error_message(&error, r)); - return 0; - } - - return 0; -} - static int reset_audit_loginuid(void) { _cleanup_free_ char *p = NULL; int r; @@ -2803,427 +1536,6 @@ static int reset_audit_loginuid(void) { return 0; } -#define HOST_HASH_KEY SD_ID128_MAKE(1a,37,6f,c7,46,ec,45,0b,ad,a3,d5,31,06,60,5d,b1) -#define CONTAINER_HASH_KEY SD_ID128_MAKE(c3,c4,f9,19,b5,57,b2,1c,e6,cf,14,27,03,9c,ee,a2) -#define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f) - -static int generate_mac(struct ether_addr *mac, sd_id128_t hash_key, uint64_t idx) { - uint8_t result[8]; - size_t l, sz; - uint8_t *v, *i; - int r; - - l = strlen(arg_machine); - sz = sizeof(sd_id128_t) + l; - if (idx > 0) - sz += sizeof(idx); - - v = alloca(sz); - - /* fetch some persistent data unique to the host */ - r = sd_id128_get_machine((sd_id128_t*) v); - if (r < 0) - return r; - - /* combine with some data unique (on this host) to this - * container instance */ - i = mempcpy(v + sizeof(sd_id128_t), arg_machine, l); - if (idx > 0) { - idx = htole64(idx); - memcpy(i, &idx, sizeof(idx)); - } - - /* Let's hash the host machine ID plus the container name. We - * use a fixed, but originally randomly created hash key here. */ - siphash24(result, v, sz, hash_key.bytes); - - assert_cc(ETH_ALEN <= sizeof(result)); - memcpy(mac->ether_addr_octet, result, ETH_ALEN); - - /* see eth_random_addr in the kernel */ - mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ - mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ - - return 0; -} - -static int setup_veth(pid_t pid, char iface_name[IFNAMSIZ], int *ifi) { - _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; - _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; - struct ether_addr mac_host, mac_container; - int r, i; - - if (!arg_private_network) - return 0; - - if (!arg_network_veth) - return 0; - - /* Use two different interface name prefixes depending whether - * we are in bridge mode or not. */ - snprintf(iface_name, IFNAMSIZ - 1, "%s-%s", - arg_network_bridge ? "vb" : "ve", arg_machine); - - r = generate_mac(&mac_container, CONTAINER_HASH_KEY, 0); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for container side: %m"); - - r = generate_mac(&mac_host, HOST_HASH_KEY, 0); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for host side: %m"); - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, iface_name); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac_host); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container(m, VETH_INFO_PEER); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, "host0"); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac_container); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new veth interfaces (host0, %s): %m", iface_name); - - i = (int) if_nametoindex(iface_name); - if (i <= 0) - return log_error_errno(errno, "Failed to resolve interface %s: %m", iface_name); - - *ifi = i; - - return 0; -} - -static int setup_bridge(const char veth_name[], int *ifi) { - _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; - _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; - int r, bridge; - - if (!arg_private_network) - return 0; - - if (!arg_network_veth) - return 0; - - if (!arg_network_bridge) - return 0; - - bridge = (int) if_nametoindex(arg_network_bridge); - if (bridge <= 0) - return log_error_errno(errno, "Failed to resolve interface %s: %m", arg_network_bridge); - - *ifi = bridge; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP); - if (r < 0) - return log_error_errno(r, "Failed to set IFF_UP flag: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name field: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge); - if (r < 0) - return log_error_errno(r, "Failed to add netlink master field: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add veth interface to bridge: %m"); - - return 0; -} - -static int parse_interface(struct udev *udev, const char *name) { - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - char ifi_str[2 + DECIMAL_STR_MAX(int)]; - int ifi; - - ifi = (int) if_nametoindex(name); - if (ifi <= 0) - return log_error_errno(errno, "Failed to resolve interface %s: %m", name); - - sprintf(ifi_str, "n%i", ifi); - d = udev_device_new_from_device_id(udev, ifi_str); - if (!d) - return log_error_errno(errno, "Failed to get udev device for interface %s: %m", name); - - if (udev_device_get_is_initialized(d) <= 0) { - log_error("Network interface %s is not initialized yet.", name); - return -EBUSY; - } - - return ifi; -} - -static int move_network_interfaces(pid_t pid) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; - char **i; - int r; - - if (!arg_private_network) - return 0; - - if (strv_isempty(arg_network_interfaces)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, arg_network_interfaces) { - _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to append namespace PID to netlink message: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i); - } - - return 0; -} - -static int setup_macvlan(pid_t pid) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; - unsigned idx = 0; - char **i; - int r; - - if (!arg_private_network) - return 0; - - if (strv_isempty(arg_network_macvlan)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, arg_network_macvlan) { - _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; - _cleanup_free_ char *n = NULL; - struct ether_addr mac; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = generate_mac(&mac, MACVLAN_HASH_KEY, idx++); - if (r < 0) - return log_error_errno(r, "Failed to create MACVLAN MAC address: %m"); - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface index: %m"); - - n = strappend("mv-", *i); - if (!n) - return log_oom(); - - strshorten(n, IFNAMSIZ-1); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "macvlan"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_MACVLAN_MODE, MACVLAN_MODE_BRIDGE); - if (r < 0) - return log_error_errno(r, "Failed to append macvlan mode: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new macvlan interfaces: %m"); - } - - return 0; -} - -static int setup_ipvlan(pid_t pid) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_netlink_unref_ sd_netlink *rtnl = NULL; - char **i; - int r; - - if (!arg_private_network) - return 0; - - if (strv_isempty(arg_network_ipvlan)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, arg_network_ipvlan) { - _cleanup_netlink_message_unref_ sd_netlink_message *m = NULL; - _cleanup_free_ char *n = NULL; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface index: %m"); - - n = strappend("iv-", *i); - if (!n) - return log_oom(); - - strshorten(n, IFNAMSIZ-1); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "ipvlan"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_u16(m, IFLA_IPVLAN_MODE, IPVLAN_MODE_L2); - if (r < 0) - return log_error_errno(r, "Failed to add ipvlan mode: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new ipvlan interfaces: %m"); - } - - return 0; -} - static int setup_seccomp(void) { #ifdef HAVE_SECCOMP @@ -3922,247 +2234,6 @@ static void loop_remove(int nr, int *image_fd) { log_debug_errno(errno, "Failed to remove loop %d: %m", nr); } -static int spawn_getent(const char *database, const char *key, pid_t *rpid) { - int pipe_fds[2]; - pid_t pid; - - assert(database); - assert(key); - assert(rpid); - - if (pipe2(pipe_fds, O_CLOEXEC) < 0) - return log_error_errno(errno, "Failed to allocate pipe: %m"); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork getent child: %m"); - else if (pid == 0) { - int nullfd; - char *empty_env = NULL; - - if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (pipe_fds[0] > 2) - safe_close(pipe_fds[0]); - if (pipe_fds[1] > 2) - safe_close(pipe_fds[1]); - - nullfd = open("/dev/null", O_RDWR); - if (nullfd < 0) - _exit(EXIT_FAILURE); - - if (dup3(nullfd, STDIN_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (dup3(nullfd, STDERR_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (nullfd > 2) - safe_close(nullfd); - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - close_all_fds(NULL, 0); - - execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env); - execle("/bin/getent", "getent", database, key, NULL, &empty_env); - _exit(EXIT_FAILURE); - } - - pipe_fds[1] = safe_close(pipe_fds[1]); - - *rpid = pid; - - return pipe_fds[0]; -} - -static int change_uid_gid(char **_home) { - char line[LINE_MAX], *x, *u, *g, *h; - const char *word, *state; - _cleanup_free_ uid_t *uids = NULL; - _cleanup_free_ char *home = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_close_ int fd = -1; - unsigned n_uids = 0; - size_t sz = 0, l; - uid_t uid; - gid_t gid; - pid_t pid; - int r; - - assert(_home); - - if (!arg_user || streq(arg_user, "root") || streq(arg_user, "0")) { - /* Reset everything fully to 0, just in case */ - - r = reset_uid_gid(); - if (r < 0) - return log_error_errno(r, "Failed to become root: %m"); - - *_home = NULL; - return 0; - } - - /* First, get user credentials */ - fd = spawn_getent("passwd", arg_user, &pid); - if (fd < 0) - return fd; - - f = fdopen(fd, "r"); - if (!f) - return log_oom(); - fd = -1; - - if (!fgets(line, sizeof(line), f)) { - - if (!ferror(f)) { - log_error("Failed to resolve user %s.", arg_user); - return -ESRCH; - } - - log_error_errno(errno, "Failed to read from getent: %m"); - return -errno; - } - - truncate_nl(line); - - wait_for_terminate_and_warn("getent passwd", pid, true); - - x = strchr(line, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid user field."); - return -EIO; - } - - u = strchr(x+1, ':'); - if (!u) { - log_error("/etc/passwd entry has invalid password field."); - return -EIO; - } - - u++; - g = strchr(u, ':'); - if (!g) { - log_error("/etc/passwd entry has invalid UID field."); - return -EIO; - } - - *g = 0; - g++; - x = strchr(g, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid GID field."); - return -EIO; - } - - *x = 0; - h = strchr(x+1, ':'); - if (!h) { - log_error("/etc/passwd entry has invalid GECOS field."); - return -EIO; - } - - h++; - x = strchr(h, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid home directory field."); - return -EIO; - } - - *x = 0; - - r = parse_uid(u, &uid); - if (r < 0) { - log_error("Failed to parse UID of user."); - return -EIO; - } - - r = parse_gid(g, &gid); - if (r < 0) { - log_error("Failed to parse GID of user."); - return -EIO; - } - - home = strdup(h); - if (!home) - return log_oom(); - - /* Second, get group memberships */ - fd = spawn_getent("initgroups", arg_user, &pid); - if (fd < 0) - return fd; - - fclose(f); - f = fdopen(fd, "r"); - if (!f) - return log_oom(); - fd = -1; - - if (!fgets(line, sizeof(line), f)) { - if (!ferror(f)) { - log_error("Failed to resolve user %s.", arg_user); - return -ESRCH; - } - - log_error_errno(errno, "Failed to read from getent: %m"); - return -errno; - } - - truncate_nl(line); - - wait_for_terminate_and_warn("getent initgroups", pid, true); - - /* Skip over the username and subsequent separator whitespace */ - x = line; - x += strcspn(x, WHITESPACE); - x += strspn(x, WHITESPACE); - - FOREACH_WORD(word, l, x, state) { - char c[l+1]; - - memcpy(c, word, l); - c[l] = 0; - - if (!GREEDY_REALLOC(uids, sz, n_uids+1)) - return log_oom(); - - r = parse_uid(c, &uids[n_uids++]); - if (r < 0) { - log_error("Failed to parse group data from getent."); - return -EIO; - } - } - - r = mkdir_parents(home, 0775); - if (r < 0) - return log_error_errno(r, "Failed to make home root directory: %m"); - - r = mkdir_safe(home, 0755, uid, gid); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Failed to make home directory: %m"); - - (void) fchown(STDIN_FILENO, uid, gid); - (void) fchown(STDOUT_FILENO, uid, gid); - (void) fchown(STDERR_FILENO, uid, gid); - - if (setgroups(n_uids, uids) < 0) - return log_error_errno(errno, "Failed to set auxiliary groups: %m"); - - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "setregid() failed: %m"); - - if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "setreuid() failed: %m"); - - if (_home) { - *_home = home; - home = NULL; - } - - return 0; -} - /* * Return values: * < 0 : wait_for_terminate() failed to get the state of the @@ -4401,7 +2472,7 @@ static int inner_child( } } - r = mount_all(NULL, true); + r = mount_all(NULL, true, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); if (r < 0) return r; @@ -4412,7 +2483,7 @@ static int inner_child( return -ESRCH; } - r = mount_systemd_cgroup_writable(""); + r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); if (r < 0) return r; @@ -4437,10 +2508,12 @@ static int inner_child( if (arg_private_network) loopback_setup(); - r = send_rtnl(rtnl_socket); - if (r < 0) - return r; - rtnl_socket = safe_close(rtnl_socket); + if (arg_expose_ports) { + r = expose_port_send_rtnl(rtnl_socket); + if (r < 0) + return r; + rtnl_socket = safe_close(rtnl_socket); + } if (drop_capabilities() < 0) return log_error_errno(errno, "drop_capabilities() failed: %m"); @@ -4461,7 +2534,7 @@ static int inner_child( return log_error_errno(errno, "setexeccon(\"%s\") failed: %m", arg_selinux_context); #endif - r = change_uid_gid(&home); + r = change_uid_gid(arg_user, &home); if (r < 0) return r; @@ -4632,11 +2705,11 @@ static int outer_child( if (mount(directory, directory, NULL, MS_BIND|MS_REC, NULL) < 0) return log_error_errno(errno, "Failed to make bind mount: %m"); - r = setup_volatile(directory); + r = setup_volatile(directory, arg_volatile_mode, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_context); if (r < 0) return r; - r = setup_volatile_state(directory); + r = setup_volatile_state(directory, arg_volatile_mode, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_context); if (r < 0) return r; @@ -4650,16 +2723,18 @@ static int outer_child( return log_error_errno(r, "Failed to make tree read-only: %m"); } - r = mount_all(directory, false); + r = mount_all(directory, false, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); if (r < 0) return r; - if (copy_devnodes(directory) < 0) + r = copy_devnodes(directory); + if (r < 0) return r; dev_setup(directory, arg_uid_shift, arg_uid_shift); - if (setup_pts(directory) < 0) + r = setup_pts(directory); + if (r < 0) return r; r = setup_propagate(directory); @@ -4686,11 +2761,11 @@ static int outer_child( if (r < 0) return r; - r = mount_custom(directory); + r = mount_custom(directory, arg_custom_mounts, arg_n_custom_mounts, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); if (r < 0) return r; - r = mount_cgroups(directory); + r = mount_cgroups(directory, arg_unified_cgroup_hierarchy, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); if (r < 0) return r; @@ -4754,141 +2829,6 @@ static int setup_uid_map(pid_t pid) { return 0; } -static int chown_cgroup(pid_t pid) { - _cleanup_free_ char *path = NULL, *fs = NULL; - _cleanup_close_ int fd = -1; - const char *fn; - int r; - - r = cg_pid_get_path(NULL, pid, &path); - if (r < 0) - return log_error_errno(r, "Failed to get container cgroup path: %m"); - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); - if (r < 0) - return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); - - fd = open(fs, O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", fs); - - FOREACH_STRING(fn, - ".", - "tasks", - "notify_on_release", - "cgroup.procs", - "cgroup.clone_children", - "cgroup.controllers", - "cgroup.subtree_control", - "cgroup.populated") - if (fchownat(fd, fn, arg_uid_shift, arg_uid_shift, 0) < 0) - log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, - "Failed to chown() cgroup file %s, ignoring: %m", fn); - - return 0; -} - -static int sync_cgroup(pid_t pid) { - _cleanup_free_ char *cgroup = NULL; - char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1]; - bool undo_mount = false; - const char *fn; - int unified, r; - - unified = cg_unified(); - if (unified < 0) - return log_error_errno(unified, "Failed to determine whether the unified hierachy is used: %m"); - - if ((unified > 0) == arg_unified_cgroup_hierarchy) - return 0; - - /* When the host uses the legacy cgroup setup, but the - * container shall use the unified hierarchy, let's make sure - * we copy the path from the name=systemd hierarchy into the - * unified hierarchy. Similar for the reverse situation. */ - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); - if (r < 0) - return log_error_errno(r, "Failed to get control group of " PID_FMT ": %m", pid); - - /* In order to access the unified hierarchy we need to mount it */ - if (!mkdtemp(tree)) - return log_error_errno(errno, "Failed to generate temporary mount point for unified hierarchy: %m"); - - if (unified) - r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr"); - else - r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "__DEVEL__sane_behavior"); - if (r < 0) { - r = log_error_errno(errno, "Failed to mount unified hierarchy: %m"); - goto finish; - } - - undo_mount = true; - - fn = strjoina(tree, cgroup, "/cgroup.procs"); - (void) mkdir_parents(fn, 0755); - - sprintf(pid_string, PID_FMT, pid); - r = write_string_file(fn, pid_string, 0); - if (r < 0) - log_error_errno(r, "Failed to move process: %m"); - -finish: - if (undo_mount) - (void) umount(tree); - - (void) rmdir(tree); - return r; -} - -static int create_subcgroup(pid_t pid) { - _cleanup_free_ char *cgroup = NULL; - const char *child; - int unified, r; - CGroupMask supported; - - /* In the unified hierarchy inner nodes may only only contain - * subgroups, but not processes. Hence, if we running in the - * unified hierarchy and the container does the same, and we - * did not create a scope unit for the container move us and - * the container into two separate subcgroups. */ - - if (!arg_keep_unit) - return 0; - - if (!arg_unified_cgroup_hierarchy) - return 0; - - unified = cg_unified(); - if (unified < 0) - return log_error_errno(unified, "Failed to determine whether the unified hierachy is used: %m"); - if (unified == 0) - return 0; - - r = cg_mask_supported(&supported); - if (r < 0) - return log_error_errno(r, "Failed to determine supported controllers: %m"); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); - if (r < 0) - return log_error_errno(r, "Failed to get our control group: %m"); - - child = strjoina(cgroup, "/payload"); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, pid); - if (r < 0) - return log_error_errno(r, "Failed to create %s subcgroup: %m", child); - - child = strjoina(cgroup, "/supervisor"); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, 0); - if (r < 0) - return log_error_errno(r, "Failed to create %s subcgroup: %m", child); - - /* Try to enable as many controllers as possible for the new payload. */ - (void) cg_enable_everywhere(supported, supported, cgroup); - return 0; -} - static int load_settings(void) { _cleanup_(settings_freep) Settings *settings = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -5332,23 +3272,23 @@ int main(int argc, char *argv[]) { goto finish; } - if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) { + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) { r = log_error_errno(errno, "Failed to create kmsg socket pair: %m"); goto finish; } - if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) { + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) { r = log_error_errno(errno, "Failed to create rtnl socket pair: %m"); goto finish; } - if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, pid_socket_pair) < 0) { + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pid_socket_pair) < 0) { r = log_error_errno(errno, "Failed to create pid socket pair: %m"); goto finish; } if (arg_userns) - if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, uid_shift_socket_pair) < 0) { + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uid_shift_socket_pair) < 0) { r = log_error_errno(errno, "Failed to create uid shift socket pair: %m"); goto finish; } @@ -5419,6 +3359,7 @@ int main(int argc, char *argv[]) { kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]); rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]); pid_socket_pair[1] = safe_close(pid_socket_pair[1]); + uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); /* Wait for the outer child. */ r = wait_for_terminate_and_warn("namespace helper", pid, NULL); @@ -5437,7 +3378,7 @@ int main(int argc, char *argv[]) { goto finish; } if (l != sizeof(pid)) { - log_error("Short read while reading inner child PID: %m"); + log_error("Short read while reading inner child PID."); r = EIO; goto finish; } @@ -5457,7 +3398,7 @@ int main(int argc, char *argv[]) { goto finish; } if (l != sizeof(arg_uid_shift)) { - log_error("Short read while reading UID shift: %m"); + log_error("Short read while reading UID shift."); r = EIO; goto finish; } @@ -5469,39 +3410,64 @@ int main(int argc, char *argv[]) { (void) barrier_place(&barrier); /* #2 */ } - r = move_network_interfaces(pid); - if (r < 0) - goto finish; + if (arg_private_network) { - r = setup_veth(pid, veth_name, &ifi); - if (r < 0) - goto finish; + r = move_network_interfaces(pid, arg_network_interfaces); + if (r < 0) + goto finish; - r = setup_bridge(veth_name, &ifi); - if (r < 0) - goto finish; + if (arg_network_veth) { + r = setup_veth(arg_machine, pid, veth_name, !!arg_network_bridge); + if (r < 0) + goto finish; + else if (r > 0) + ifi = r; - r = setup_macvlan(pid); - if (r < 0) - goto finish; + if (arg_network_bridge) { + r = setup_bridge(veth_name, arg_network_bridge); + if (r < 0) + goto finish; + if (r > 0) + ifi = r; + } + } - r = setup_ipvlan(pid); - if (r < 0) - goto finish; + r = setup_macvlan(arg_machine, pid, arg_network_macvlan); + if (r < 0) + goto finish; - r = register_machine(pid, ifi); - if (r < 0) - goto finish; + r = setup_ipvlan(arg_machine, pid, arg_network_ipvlan); + if (r < 0) + goto finish; + } - r = sync_cgroup(pid); - if (r < 0) - goto finish; + if (arg_register) { + r = register_machine( + arg_machine, + pid, + arg_directory, + arg_uuid, + ifi, + arg_slice, + arg_custom_mounts, arg_n_custom_mounts, + arg_kill_signal, + arg_property, + arg_keep_unit); + if (r < 0) + goto finish; + } - r = create_subcgroup(pid); + r = sync_cgroup(pid, arg_unified_cgroup_hierarchy); if (r < 0) goto finish; - r = chown_cgroup(pid); + if (arg_keep_unit) { + r = create_subcgroup(pid, arg_unified_cgroup_hierarchy); + if (r < 0) + goto finish; + } + + r = chown_cgroup(pid, arg_uid_shift); if (r < 0) goto finish; @@ -5554,11 +3520,11 @@ int main(int argc, char *argv[]) { sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL); if (arg_expose_ports) { - r = watch_rtnl(event, rtnl_socket_pair[0], &exposed, &rtnl); + r = expose_port_watch_rtnl(event, rtnl_socket_pair[0], on_address_change, &exposed, &rtnl); if (r < 0) goto finish; - (void) expose_ports(rtnl, &exposed); + (void) expose_port_execute(rtnl, arg_expose_ports, &exposed); } rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); @@ -5583,7 +3549,8 @@ int main(int argc, char *argv[]) { putc('\n', stdout); /* Kill if it is not dead yet anyway */ - terminate_machine(pid); + if (arg_register && !arg_keep_unit) + terminate_machine(pid); /* Normally redundant, but better safe than sorry */ kill(pid, SIGKILL); @@ -5621,7 +3588,7 @@ int main(int argc, char *argv[]) { break; } - flush_ports(&exposed); + expose_port_flush(arg_expose_ports, &exposed); } finish: @@ -5653,7 +3620,7 @@ finish: (void) rm_rf(p, REMOVE_ROOT); } - flush_ports(&exposed); + expose_port_flush(arg_expose_ports, &exposed); free(arg_directory); free(arg_template); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index d9523d37f2..96c39aa0a9 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -1472,6 +1472,7 @@ static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, cha "Requisite\0" "RequisiteOverridable\0" "Wants\0" + "ConsistsOf\0" "BindsTo\0", [DEPENDENCY_REVERSE] = "RequiredBy\0" "RequiredByOverridable\0" |