From patchwork Mon Apr 18 03:29:12 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Timo Rothenpieler X-Patchwork-Id: 2374 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.27.255.55]) by backend41.mail.ord1d.rsapps.net with LMTP id kPs0CCVoXWIfYQAAqwncew (envelope-from ) for ; Mon, 18 Apr 2022 09:31:17 -0400 Received: from proxy10.mail.iad3a.rsapps.net ([172.27.255.55]) by director12.mail.ord1d.rsapps.net with LMTP id 2HteICVoXWKNQwAAIasKDg (envelope-from ) for ; Mon, 18 Apr 2022 09:31:17 -0400 Received: from smtp33.gate.iad3a ([172.27.255.55]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy10.mail.iad3a.rsapps.net with LMTPS id iMbYGSVoXWI1AwAAnQ/bqA (envelope-from ) for ; Mon, 18 Apr 2022 09:31:17 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp33.gate.iad3a.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=rothenpieler.org; dmarc=fail (p=none; dis=none) header.from=rothenpieler.org X-Suspicious-Flag: YES X-Classification-ID: d2867342-bf1b-11ec-8adf-525400201c3f-1-1 Received: from [216.105.38.7] ([216.105.38.7:34010] helo=lists.sourceforge.net) by smtp33.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id F9/B6-24440-4286D526; Mon, 18 Apr 2022 09:31:16 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.94.2) (envelope-from ) id 1ngRRs-0000fz-HX; Mon, 18 Apr 2022 13:29:47 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ngRRr-0000fs-Kw for openvpn-devel@lists.sourceforge.net; Mon, 18 Apr 2022 13:29:46 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=pOZbseLa9U37XLh5Bj+8gxG7322+kNoXs/9GvBOz4Kw=; b=bGvkWGQdz0qgpE424Dx5QcB8gw HKZYNUHgbf/ip0GIZYauuV9v/Xnlgbe4JUrDNWZiaKcAAA9Eh7u+61Net0wV0DQ2xC5zBdoEb+LhL vKjmEKmdUfokdVTU8kbVFc5xbtFsrRd08NODt0lif5Q/+TFuDpihgjcMiaaIRJSD9kuY=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=pOZbseLa9U37XLh5Bj+8gxG7322+kNoXs/9GvBOz4Kw=; b=a4CvxV1fRqeOB2l0QfGjr8KuhO XJecjrmdwnxpBNS+mNIpqQcyFDTjlh8rkVqz+LTUYX7Ei33HMstzU8GsvdXIunhocSbgv6Mu6czKv ReHdsuPg8x8cKfrXatdo2C3espz6hnjtJNLyxJ3uJGFXp8H+Ih2pH+jJJCwIkc71OjAs=; Received: from btbn.de ([136.243.74.85]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.94.2) id 1ngRRl-00330x-Om for openvpn-devel@lists.sourceforge.net; Mon, 18 Apr 2022 13:29:45 +0000 Received: from [authenticated] by btbn.de (Postfix) with ESMTPSA id 4F43831056C; Mon, 18 Apr 2022 15:29:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=rothenpieler.org; s=mail; t=1650288566; bh=pOZbseLa9U37XLh5Bj+8gxG7322+kNoXs/9GvBOz4Kw=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=nvhbglzNP7ssAknPVdQ3jurE+Vg1mf+9eN3JP6JXwA6Tiy3J6cqVxpHISQi4e/d8b wtxrliyfR1kcHgFDtMmjMS2KtO5seeRKJUuQUyT1t4wMrsA5sS28EkA2RZ+6T1jNRJ /oZ/xPwfvUXBdPOvq+Y7Z+B3OzFEyf1bcerEVI6akLbrfrNUbBvRkWgTKvAqE4CGGX HrA+O+RR/dtFJPYHg0UEYFEznOo1P/nU+7HBKbUBI10WS9McWDKrGocgk3feaQAngY dZnvL7UVbn1Y5DwQHNYQZPs/VqXDHlDUvcwQviwiBORNqgcv+V3Y6A6vWd3C1rsAs8 My0tAWjPMlQMA== From: Timo Rothenpieler To: openvpn-devel@lists.sourceforge.net Date: Mon, 18 Apr 2022 15:29:12 +0200 Message-Id: <20220418132912.267-1-timo@rothenpieler.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220329192934.1415-1-timo@rothenpieler.org> References: <20220329192934.1415-1-timo@rothenpieler.org> MIME-Version: 1.0 X-Spam-Report: Spam detection software, running on the system "util-spamd-1.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: On Linux, when dropping privileges, interaction with the network configuration, such as tearing down routes or ovpn-dco interfaces will fail when --user/--group are used. This patch sets the CAP_NET_ADMIN capability, which grants the needed privileges during the lifetime of the OpenVPN process when dropping root privileges. Content analysis details: (-0.2 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain X-Headers-End: 1ngRRl-00330x-Om Subject: [Openvpn-devel] [PATCH] platform: Retain CAP_NET_ADMIN when dropping privileges X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: David Sommerseth Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox On Linux, when dropping privileges, interaction with the network configuration, such as tearing down routes or ovpn-dco interfaces will fail when --user/--group are used. This patch sets the CAP_NET_ADMIN capability, which grants the needed privileges during the lifetime of the OpenVPN process when dropping root privileges. Signed-off-by: Timo Rothenpieler Reviewed-By: David Sommerseth --- configure.ac | 19 ++++ distro/systemd/openvpn-client@.service.in | 2 +- distro/systemd/openvpn-server@.service.in | 2 +- src/openvpn/init.c | 30 +++++- src/openvpn/platform.c | 107 +++++++++++++++++++++- src/openvpn/platform.h | 7 +- 6 files changed, 158 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index 85921ddb..d2eb3426 100644 --- a/configure.ac +++ b/configure.ac @@ -794,6 +794,25 @@ dnl esac fi +dnl +dnl Depend on libcap-ng on Linux +dnl +case "$host" in + *-*-linux*) + PKG_CHECK_MODULES([LIBCAPNG], + [libcap-ng], + [], + [AC_MSG_ERROR([libcap-ng package not found. Is the development package and pkg-config installed?])] + ) + AC_CHECK_HEADER([sys/prctl.h],,[AC_MSG_ERROR([sys/prctl.h not found!])]) + + CFLAGS="${CFLAGS} ${LIBCAPNG_CFALGS}" + LIBS="${LIBS} ${LIBCAPNG_LIBS}" + AC_DEFINE(HAVE_LIBCAPNG, 1, [Enable libcap-ng support]) + ;; +esac + + if test "${with_crypto_library}" = "openssl"; then AC_ARG_VAR([OPENSSL_CFLAGS], [C compiler flags for OpenSSL]) AC_ARG_VAR([OPENSSL_LIBS], [linker flags for OpenSSL]) diff --git a/distro/systemd/openvpn-client@.service.in b/distro/systemd/openvpn-client@.service.in index cbcef653..159fb4dc 100644 --- a/distro/systemd/openvpn-client@.service.in +++ b/distro/systemd/openvpn-client@.service.in @@ -11,7 +11,7 @@ Type=notify PrivateTmp=true WorkingDirectory=/etc/openvpn/client ExecStart=@sbindir@/openvpn --suppress-timestamps --nobind --config %i.conf -CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE +CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_SYS_CHROOT CAP_DAC_OVERRIDE LimitNPROC=10 DeviceAllow=/dev/null rw DeviceAllow=/dev/net/tun rw diff --git a/distro/systemd/openvpn-server@.service.in b/distro/systemd/openvpn-server@.service.in index d1cc72cb..6e8e7d94 100644 --- a/distro/systemd/openvpn-server@.service.in +++ b/distro/systemd/openvpn-server@.service.in @@ -11,7 +11,7 @@ Type=notify PrivateTmp=true WorkingDirectory=/etc/openvpn/server ExecStart=@sbindir@/openvpn --status %t/openvpn-server/status-%i.log --status-version 2 --suppress-timestamps --config %i.conf -CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_AUDIT_WRITE +CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_AUDIT_WRITE LimitNPROC=10 DeviceAllow=/dev/null rw DeviceAllow=/dev/net/tun rw diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 857e52ef..559f7cda 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -1138,6 +1138,30 @@ possibly_become_daemon(const struct options *options) return ret; } +/* + * Determine if we need to retain process capabilities. DCO and SITNL need it. + * Enforce it for DCO, but only try and soft-fail for SITNL to keep backwards compat. + * + * Returns the tri-state expected by platform_user_group_set. + * -1: try to keep caps, but continue if impossible + * 0: don't keep caps + * 1: keep caps, fail hard if impossible + */ +static int +need_keep_caps(struct context *c) +{ + if (dco_enabled(&c->options)) + { + return 1; + } + +#ifdef ENABLE_SITNL + return -1; +#else + return 0; +#endif +} + /* * Actually do UID/GID downgrade, chroot and SELinux context switching, if requested. */ @@ -1167,8 +1191,10 @@ do_uid_gid_chroot(struct context *c, bool no_delay) { if (no_delay) { - platform_group_set(&c0->platform_state_group); - platform_user_set(&c0->platform_state_user); + int keep_caps = need_keep_caps(c); + platform_user_group_set(&c0->platform_state_user, + &c0->platform_state_group, + keep_caps); } else if (c->first_time) { diff --git a/src/openvpn/platform.c b/src/openvpn/platform.c index 450f28ba..5a81e8d0 100644 --- a/src/openvpn/platform.c +++ b/src/openvpn/platform.c @@ -43,6 +43,11 @@ #include #endif +#ifdef HAVE_LIBCAPNG +#include +#include +#endif + /* Redefine the top level directory of the filesystem * to restrict access to files for security */ void @@ -91,7 +96,7 @@ platform_user_get(const char *username, struct platform_state_user *state) return ret; } -void +static void platform_user_set(const struct platform_state_user *state) { #if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) @@ -130,7 +135,7 @@ platform_group_get(const char *groupname, struct platform_state_group *state) return ret; } -void +static void platform_group_set(const struct platform_state_group *state) { #if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) @@ -155,6 +160,104 @@ platform_group_set(const struct platform_state_group *state) #endif } +/* Set user and group, retaining neccesary capabilities required by the platform. + * + * The keep_caps argument has 3 possible states: + * >0: Retain capabilities, and fail hard on failure to do so. + * ==0: Don't attempt to retain any capabilities, just sitch user/group. + * <0: Try to retain capabilities, but continue on failure. + */ +void platform_user_group_set(const struct platform_state_user *user_state, + const struct platform_state_group *group_state, + int keep_caps) +{ + unsigned int err_flags = (keep_caps > 0) ? M_FATAL : M_NONFATAL; +#ifdef HAVE_LIBCAPNG + int new_gid = -1, new_uid = -1; + int res; + + if (keep_caps == 0) + { + goto fallback; + } + + /* + * new_uid/new_gid defaults to -1, which will not make + * libcap-ng change the UID/GID unless configured + */ + if (group_state->groupname && group_state->gr) + { + new_gid = group_state->gr->gr_gid; + } + if (user_state->username && user_state->pw) + { + new_uid = user_state->pw->pw_uid; + } + + /* Prepare capabilities before dropping UID/GID */ + capng_clear(CAPNG_SELECT_BOTH); + res = capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, CAP_NET_ADMIN); + if (res < 0) + { + msg(err_flags, "capng_update(CAP_NET_ADMIN) failed: %d", res); + goto fallback; + } + + /* Change to new UID/GID. + * capng_change_id() internally calls capng_apply() to apply prepared capabilities. + */ + res = capng_change_id(new_uid, new_gid, CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING); + if (res == -4 || res == -6) + { + /* -4 and -6 mean failure of setuid/gid respectively. + There is no point for us to continue if those failed. */ + msg(M_ERR, "capng_change_id('%s','%s') failed: %d", + user_state->username, group_state->groupname, res); + } + else if (res == -3) + { + msg(M_NONFATAL | M_ERRNO, "capng_change_id() failed applying capabilities"); + msg(err_flags, "NOTE: previous error likely due to missing capability CAP_SETPCAP."); + goto fallback; + } + else if (res < 0) + { + msg(err_flags | M_ERRNO, "capng_change_id('%s','%s') failed retaining capabilities: %d", + user_state->username, group_state->groupname, res); + goto fallback; + } + + if (new_uid >= 0) + { + msg(M_INFO, "UID set to %s", user_state->username); + } + if (new_gid >= 0) + { + msg(M_INFO, "GID set to %s", group_state->groupname); + } + + msg(M_INFO, "Capabilities retained: CAP_NET_ADMIN"); + return; + +fallback: + /* capng_change_id() can leave this flag clobbered on failure + * This is working around a bug in libcap-ng, which can leave the flag set + * on failure: https://github.com/stevegrubb/libcap-ng/issues/33 */ + if (prctl(PR_GET_KEEPCAPS) && prctl(PR_SET_KEEPCAPS, 0) < 0) + { + msg(M_ERR, "Clearing KEEPCAPS flag failed"); + } +#endif /* HAVE_LIBCAPNG */ + + if (keep_caps) + { + msg(err_flags, "Unable to retain capabilities"); + } + + platform_group_set(group_state); + platform_user_set(user_state); +} + /* Change process priority */ void platform_nice(int niceval) diff --git a/src/openvpn/platform.h b/src/openvpn/platform.h index a3eec298..19187d3a 100644 --- a/src/openvpn/platform.h +++ b/src/openvpn/platform.h @@ -79,11 +79,12 @@ struct platform_state_group { bool platform_user_get(const char *username, struct platform_state_user *state); -void platform_user_set(const struct platform_state_user *state); - bool platform_group_get(const char *groupname, struct platform_state_group *state); -void platform_group_set(const struct platform_state_group *state); +void platform_user_group_set(const struct platform_state_user *user_state, + const struct platform_state_group *group_state, + int keep_caps); + /* * Extract UID or GID