diff --git a/src/core/socket.c b/src/core/socket.c index ec901fbdd7..891a2c8fdb 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -29,6 +29,7 @@ #include #include "alloc-util.h" +#include "bpf-firewall.h" #include "bus-error.h" #include "bus-util.h" #include "copy.h" @@ -37,6 +38,7 @@ #include "exit-status.h" #include "fd-util.h" #include "format-util.h" +#include "in-addr-util.h" #include "io-util.h" #include "label.h" #include "log.h" @@ -56,7 +58,6 @@ #include "unit-name.h" #include "unit.h" #include "user-util.h" -#include "in-addr-util.h" struct SocketPeer { unsigned n_ref; @@ -1437,6 +1438,102 @@ no_label: return 0; } +static int socket_address_listen_do( + Socket *s, + const SocketAddress *address, + const char *label) { + + assert(s); + assert(address); + + return socket_address_listen( + address, + SOCK_CLOEXEC|SOCK_NONBLOCK, + s->backlog, + s->bind_ipv6_only, + s->bind_to_device, + s->reuse_port, + s->free_bind, + s->transparent, + s->directory_mode, + s->socket_mode, + label); +} + +static int socket_address_listen_in_cgroup( + Socket *s, + const SocketAddress *address, + const char *label) { + + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + int fd, r; + pid_t pid; + + assert(s); + assert(address); + + /* This is a wrapper around socket_address_listen(), that forks off a helper process inside the socket's cgroup + * in which the socket is actually created. This way we ensure the socket is actually properly attached to the + * unit's cgroup for the purpose of BPF filtering and such. */ + + if (!IN_SET(address->sockaddr.sa.sa_family, AF_INET, AF_INET6)) + goto shortcut; /* BPF filtering only applies to IPv4 + IPv6, shortcut things for other protocols */ + + r = bpf_firewall_supported(); + if (r < 0) + return r; + if (r == 0) /* If BPF firewalling isn't supported anyway — there's no point in this forking complexity */ + goto shortcut; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0) + return log_unit_error_errno(UNIT(s), errno, "Failed to create communication channel: %m"); + + r = unit_fork_helper_process(UNIT(s), &pid); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to fork off listener stub process: %m"); + if (r == 0) { + /* Child */ + + pair[0] = safe_close(pair[0]); + + fd = socket_address_listen_do(s, address, label); + if (fd < 0) { + log_unit_error_errno(UNIT(s), fd, "Failed to create listening socket: %m"); + _exit(EXIT_FAILURE); + } + + r = send_one_fd(pair[1], fd, 0); + if (r < 0) { + log_unit_error_errno(UNIT(s), r, "Failed to send listening socket to parent: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + fd = receive_one_fd(pair[0], 0); + + /* We synchronously wait for the helper, as it shouldn't be slow */ + r = wait_for_terminate_and_warn("listen-cgroup-helper", pid, false); + if (r < 0) { + safe_close(fd); + return r; + } + + if (fd < 0) + return log_unit_error_errno(UNIT(s), fd, "Failed to receive listening socket: %m"); + + return fd; + +shortcut: + fd = socket_address_listen_do(s, address, label); + if (fd < 0) + return log_error_errno(fd, "Failed to create listening socket: %m"); + + return fd; +} + static int socket_open_fds(Socket *s) { _cleanup_(mac_selinux_freep) char *label = NULL; bool know_label = false; @@ -1480,18 +1577,7 @@ static int socket_open_fds(Socket *s) { break; } - r = socket_address_listen( - &p->address, - SOCK_CLOEXEC|SOCK_NONBLOCK, - s->backlog, - s->bind_ipv6_only, - s->bind_to_device, - s->reuse_port, - s->free_bind, - s->transparent, - s->directory_mode, - s->socket_mode, - label); + r = socket_address_listen_in_cgroup(s, &p->address, label); if (r < 0) goto rollback; @@ -1829,27 +1915,23 @@ static int socket_chown(Socket *s, pid_t *_pid) { /* We have to resolve the user names out-of-process, hence * let's fork here. It's messy, but well, what can we do? */ - pid = fork(); - if (pid < 0) - return -errno; - - if (pid == 0) { - SocketPort *p; + r = unit_fork_helper_process(UNIT(s), &pid); + if (r < 0) + return r; + if (r == 0) { uid_t uid = UID_INVALID; gid_t gid = GID_INVALID; - int ret; + SocketPort *p; - (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); - (void) ignore_signals(SIGPIPE, -1); - log_forget_fds(); + /* Child */ if (!isempty(s->user)) { const char *user = s->user; r = get_user_creds(&user, &uid, &gid, NULL, NULL); if (r < 0) { - ret = EXIT_USER; - goto fail_child; + log_unit_error_errno(UNIT(s), r, "Failed to resolve user %s: %m", user); + _exit(EXIT_USER); } } @@ -1858,8 +1940,8 @@ static int socket_chown(Socket *s, pid_t *_pid) { r = get_group_creds(&group, &gid); if (r < 0) { - ret = EXIT_GROUP; - goto fail_child; + log_unit_error_errno(UNIT(s), r, "Failed to resolve group %s: %m", group); + _exit(EXIT_GROUP); } } @@ -1875,19 +1957,12 @@ static int socket_chown(Socket *s, pid_t *_pid) { continue; if (chown(path, uid, gid) < 0) { - r = -errno; - ret = EXIT_CHOWN; - goto fail_child; + log_unit_error_errno(UNIT(s), errno, "Failed to chown(): %m"); + _exit(EXIT_CHOWN); } } - _exit(0); - - fail_child: - log_open(); - log_error_errno(r, "Failed to chown socket at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); - - _exit(ret); + _exit(EXIT_SUCCESS); } r = unit_watch_pid(UNIT(s), pid); @@ -2699,6 +2774,97 @@ _pure_ static bool socket_check_gc(Unit *u) { return s->n_connections > 0; } +static int socket_accept_do(Socket *s, int fd) { + int cfd; + + assert(s); + assert(fd >= 0); + + for (;;) { + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK); + if (cfd < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + + break; + } + + return cfd; +} + +static int socket_accept_in_cgroup(Socket *s, SocketPort *p, int fd) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + int cfd, r; + pid_t pid; + + assert(s); + assert(p); + assert(fd >= 0); + + /* Similar to socket_address_listen_in_cgroup(), but for accept() rathern than socket(): make sure that any + * connection socket is also properly associated with the cgroup. */ + + if (!IN_SET(p->address.sockaddr.sa.sa_family, AF_INET, AF_INET6)) + goto shortcut; + + r = bpf_firewall_supported(); + if (r < 0) + return r; + if (r == 0) + goto shortcut; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0) + return log_unit_error_errno(UNIT(s), errno, "Failed to create communication channel: %m"); + + r = unit_fork_helper_process(UNIT(s), &pid); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to fork off accept stub process: %m"); + if (r == 0) { + /* Child */ + + pair[0] = safe_close(pair[0]); + + cfd = socket_accept_do(s, fd); + if (cfd < 0) { + log_unit_error_errno(UNIT(s), cfd, "Failed to accept connection socket: %m"); + _exit(EXIT_FAILURE); + } + + r = send_one_fd(pair[1], cfd, 0); + if (r < 0) { + log_unit_error_errno(UNIT(s), r, "Failed to send connection socket to parent: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + cfd = receive_one_fd(pair[0], 0); + + /* We synchronously wait for the helper, as it shouldn't be slow */ + r = wait_for_terminate_and_warn("accept-cgroup-helper", pid, false); + if (r < 0) { + safe_close(cfd); + return r; + } + + if (cfd < 0) + return log_unit_error_errno(UNIT(s), cfd, "Failed to receive connection socket: %m"); + + return cfd; + +shortcut: + cfd = socket_accept_do(s, fd); + if (cfd < 0) + return log_unit_error_errno(UNIT(s), cfd, "Failed to accept connection socket: %m"); + + return cfd; +} + static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { SocketPort *p = userdata; int cfd = -1; @@ -2724,20 +2890,9 @@ static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, p->type == SOCKET_SOCKET && socket_address_can_accept(&p->address)) { - for (;;) { - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK); - if (cfd < 0) { - - if (errno == EINTR) - continue; - - log_unit_error_errno(UNIT(p->socket), errno, "Failed to accept socket: %m"); - goto fail; - } - - break; - } + cfd = socket_accept_in_cgroup(p->socket, p, fd); + if (cfd < 0) + goto fail; socket_apply_socket_options(p->socket, cfd); } diff --git a/src/core/unit.c b/src/core/unit.c index dc709d7ca4..bb40baf236 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -4476,3 +4476,43 @@ void unit_set_exec_params(Unit *s, ExecParameters *p) { c = unit_get_cgroup_context(s); SET_FLAG(p->flags, EXEC_CGROUP_DELEGATE, c && c->delegate); } + +int unit_fork_helper_process(Unit *u, pid_t *ret) { + pid_t pid; + int r; + + assert(u); + assert(ret); + + /* Forks off a helper process and makes sure it is a member of the unit's cgroup. Returns == 0 in the child, + * and > 0 in the parent. The pid parameter is always filled in with the child's PID. */ + + (void) unit_realize_cgroup(u); + + pid = fork(); + if (pid < 0) + return -errno; + + if (pid == 0) { + + (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); + (void) ignore_signals(SIGPIPE, -1); + + log_close(); + log_open(); + + if (u->cgroup_path) { + r = cg_attach_everywhere(u->manager->cgroup_supported, u->cgroup_path, 0, NULL, NULL); + if (r < 0) { + log_unit_error_errno(u, r, "Failed to join unit cgroup %s: %m", u->cgroup_path); + _exit(EXIT_CGROUP); + } + } + + *ret = getpid_cached(); + return 0; + } + + *ret = pid; + return 1; +} diff --git a/src/core/unit.h b/src/core/unit.h index 598cc6ede6..a707e5259d 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -682,6 +682,8 @@ bool unit_shall_confirm_spawn(Unit *u); void unit_set_exec_params(Unit *s, ExecParameters *p); +int unit_fork_helper_process(Unit *u, pid_t *ret); + /* Macros which append UNIT= or USER_UNIT= to the message */ #define log_unit_full(unit, level, error, ...) \