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 <timo@rothenpieler.org>
Reviewed-By: David Sommerseth <davids@openvpn.net>
Acked-by: Frank Lichtenheld <frank@lichtenheld.com>
Message-Id: <20220514103717.235-1-timo@rothenpieler.org>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg24360.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
| ... | ... |
@@ -793,6 +793,25 @@ dnl |
| 793 | 793 |
esac |
| 794 | 794 |
fi |
| 795 | 795 |
|
| 796 |
+dnl |
|
| 797 |
+dnl Depend on libcap-ng on Linux |
|
| 798 |
+dnl |
|
| 799 |
+case "$host" in |
|
| 800 |
+ *-*-linux*) |
|
| 801 |
+ PKG_CHECK_MODULES([LIBCAPNG], |
|
| 802 |
+ [libcap-ng], |
|
| 803 |
+ [], |
|
| 804 |
+ [AC_MSG_ERROR([libcap-ng package not found. Is the development package and pkg-config installed?])] |
|
| 805 |
+ ) |
|
| 806 |
+ AC_CHECK_HEADER([sys/prctl.h],,[AC_MSG_ERROR([sys/prctl.h not found!])]) |
|
| 807 |
+ |
|
| 808 |
+ CFLAGS="${CFLAGS} ${LIBCAPNG_CFALGS}"
|
|
| 809 |
+ LIBS="${LIBS} ${LIBCAPNG_LIBS}"
|
|
| 810 |
+ AC_DEFINE(HAVE_LIBCAPNG, 1, [Enable libcap-ng support]) |
|
| 811 |
+ ;; |
|
| 812 |
+esac |
|
| 813 |
+ |
|
| 814 |
+ |
|
| 796 | 815 |
if test "${with_crypto_library}" = "openssl"; then
|
| 797 | 816 |
AC_ARG_VAR([OPENSSL_CFLAGS], [C compiler flags for OpenSSL]) |
| 798 | 817 |
AC_ARG_VAR([OPENSSL_LIBS], [linker flags for OpenSSL]) |
| ... | ... |
@@ -11,7 +11,7 @@ Type=notify |
| 11 | 11 |
PrivateTmp=true |
| 12 | 12 |
WorkingDirectory=/etc/openvpn/client |
| 13 | 13 |
ExecStart=@sbindir@/openvpn --suppress-timestamps --nobind --config %i.conf |
| 14 |
-CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE |
|
| 14 |
+CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_SYS_CHROOT CAP_DAC_OVERRIDE |
|
| 15 | 15 |
LimitNPROC=10 |
| 16 | 16 |
DeviceAllow=/dev/null rw |
| 17 | 17 |
DeviceAllow=/dev/net/tun rw |
| ... | ... |
@@ -11,7 +11,7 @@ Type=notify |
| 11 | 11 |
PrivateTmp=true |
| 12 | 12 |
WorkingDirectory=/etc/openvpn/server |
| 13 | 13 |
ExecStart=@sbindir@/openvpn --status %t/openvpn-server/status-%i.log --status-version 2 --suppress-timestamps --config %i.conf |
| 14 |
-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 |
|
| 14 |
+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 |
|
| 15 | 15 |
LimitNPROC=10 |
| 16 | 16 |
DeviceAllow=/dev/null rw |
| 17 | 17 |
DeviceAllow=/dev/net/tun rw |
| ... | ... |
@@ -1183,8 +1183,9 @@ do_uid_gid_chroot(struct context *c, bool no_delay) |
| 1183 | 1183 |
{
|
| 1184 | 1184 |
if (no_delay) |
| 1185 | 1185 |
{
|
| 1186 |
- platform_group_set(&c0->platform_state_group); |
|
| 1187 |
- platform_user_set(&c0->platform_state_user); |
|
| 1186 |
+ platform_user_group_set(&c0->platform_state_user, |
|
| 1187 |
+ &c0->platform_state_group, |
|
| 1188 |
+ c); |
|
| 1188 | 1189 |
} |
| 1189 | 1190 |
else if (c->first_time) |
| 1190 | 1191 |
{
|
| ... | ... |
@@ -29,6 +29,9 @@ |
| 29 | 29 |
|
| 30 | 30 |
#include "syshead.h" |
| 31 | 31 |
|
| 32 |
+#include "openvpn.h" |
|
| 33 |
+#include "options.h" |
|
| 34 |
+ |
|
| 32 | 35 |
#include "buffer.h" |
| 33 | 36 |
#include "crypto.h" |
| 34 | 37 |
#include "error.h" |
| ... | ... |
@@ -43,6 +46,11 @@ |
| 43 | 43 |
#include <direct.h> |
| 44 | 44 |
#endif |
| 45 | 45 |
|
| 46 |
+#ifdef HAVE_LIBCAPNG |
|
| 47 |
+#include <cap-ng.h> |
|
| 48 |
+#include <sys/prctl.h> |
|
| 49 |
+#endif |
|
| 50 |
+ |
|
| 46 | 51 |
/* Redefine the top level directory of the filesystem |
| 47 | 52 |
* to restrict access to files for security */ |
| 48 | 53 |
void |
| ... | ... |
@@ -91,7 +99,7 @@ platform_user_get(const char *username, struct platform_state_user *state) |
| 91 | 91 |
return ret; |
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 |
-void |
|
| 94 |
+static void |
|
| 95 | 95 |
platform_user_set(const struct platform_state_user *state) |
| 96 | 96 |
{
|
| 97 | 97 |
#if defined(HAVE_GETPWNAM) && defined(HAVE_SETUID) |
| ... | ... |
@@ -130,7 +138,7 @@ platform_group_get(const char *groupname, struct platform_state_group *state) |
| 130 | 130 |
return ret; |
| 131 | 131 |
} |
| 132 | 132 |
|
| 133 |
-void |
|
| 133 |
+static void |
|
| 134 | 134 |
platform_group_set(const struct platform_state_group *state) |
| 135 | 135 |
{
|
| 136 | 136 |
#if defined(HAVE_GETGRNAM) && defined(HAVE_SETGID) |
| ... | ... |
@@ -155,6 +163,140 @@ platform_group_set(const struct platform_state_group *state) |
| 155 | 155 |
#endif |
| 156 | 156 |
} |
| 157 | 157 |
|
| 158 |
+/* |
|
| 159 |
+ * Determine if we need to retain process capabilities. DCO and SITNL need it. |
|
| 160 |
+ * Enforce it for DCO, but only try and soft-fail for SITNL to keep backwards compat. |
|
| 161 |
+ * |
|
| 162 |
+ * Returns the tri-state expected by platform_user_group_set. |
|
| 163 |
+ * -1: try to keep caps, but continue if impossible |
|
| 164 |
+ * 0: don't keep caps |
|
| 165 |
+ * 1: keep caps, fail hard if impossible |
|
| 166 |
+ */ |
|
| 167 |
+static int |
|
| 168 |
+need_keep_caps(struct context *c) |
|
| 169 |
+{
|
|
| 170 |
+ if (!c) |
|
| 171 |
+ {
|
|
| 172 |
+ return -1; |
|
| 173 |
+ } |
|
| 174 |
+ |
|
| 175 |
+ if (dco_enabled(&c->options)) |
|
| 176 |
+ {
|
|
| 177 |
+#ifdef TARGET_LINUX |
|
| 178 |
+ /* DCO on Linux does not work at all without CAP_NET_ADMIN */ |
|
| 179 |
+ return 1; |
|
| 180 |
+#else |
|
| 181 |
+ /* Windows/BSD/... has no equivalent capability mechanism */ |
|
| 182 |
+ return -1; |
|
| 183 |
+#endif |
|
| 184 |
+ } |
|
| 185 |
+ |
|
| 186 |
+#ifdef ENABLE_SITNL |
|
| 187 |
+ return -1; |
|
| 188 |
+#else |
|
| 189 |
+ return 0; |
|
| 190 |
+#endif |
|
| 191 |
+} |
|
| 192 |
+ |
|
| 193 |
+/* Set user and group, retaining neccesary capabilities required by the platform. |
|
| 194 |
+ * |
|
| 195 |
+ * The keep_caps argument has 3 possible states: |
|
| 196 |
+ * >0: Retain capabilities, and fail hard on failure to do so. |
|
| 197 |
+ * ==0: Don't attempt to retain any capabilities, just sitch user/group. |
|
| 198 |
+ * <0: Try to retain capabilities, but continue on failure. |
|
| 199 |
+ */ |
|
| 200 |
+void platform_user_group_set(const struct platform_state_user *user_state, |
|
| 201 |
+ const struct platform_state_group *group_state, |
|
| 202 |
+ struct context *c) |
|
| 203 |
+{
|
|
| 204 |
+ int keep_caps = need_keep_caps(c); |
|
| 205 |
+ unsigned int err_flags = (keep_caps > 0) ? M_FATAL : M_NONFATAL; |
|
| 206 |
+#ifdef HAVE_LIBCAPNG |
|
| 207 |
+ int new_gid = -1, new_uid = -1; |
|
| 208 |
+ int res; |
|
| 209 |
+ |
|
| 210 |
+ if (keep_caps == 0) |
|
| 211 |
+ {
|
|
| 212 |
+ goto fallback; |
|
| 213 |
+ } |
|
| 214 |
+ |
|
| 215 |
+ /* |
|
| 216 |
+ * new_uid/new_gid defaults to -1, which will not make |
|
| 217 |
+ * libcap-ng change the UID/GID unless configured |
|
| 218 |
+ */ |
|
| 219 |
+ if (group_state->groupname && group_state->gr) |
|
| 220 |
+ {
|
|
| 221 |
+ new_gid = group_state->gr->gr_gid; |
|
| 222 |
+ } |
|
| 223 |
+ if (user_state->username && user_state->pw) |
|
| 224 |
+ {
|
|
| 225 |
+ new_uid = user_state->pw->pw_uid; |
|
| 226 |
+ } |
|
| 227 |
+ |
|
| 228 |
+ /* Prepare capabilities before dropping UID/GID */ |
|
| 229 |
+ capng_clear(CAPNG_SELECT_BOTH); |
|
| 230 |
+ res = capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, CAP_NET_ADMIN); |
|
| 231 |
+ if (res < 0) |
|
| 232 |
+ {
|
|
| 233 |
+ msg(err_flags, "capng_update(CAP_NET_ADMIN) failed: %d", res); |
|
| 234 |
+ goto fallback; |
|
| 235 |
+ } |
|
| 236 |
+ |
|
| 237 |
+ /* Change to new UID/GID. |
|
| 238 |
+ * capng_change_id() internally calls capng_apply() to apply prepared capabilities. |
|
| 239 |
+ */ |
|
| 240 |
+ res = capng_change_id(new_uid, new_gid, CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING); |
|
| 241 |
+ if (res == -4 || res == -6) |
|
| 242 |
+ {
|
|
| 243 |
+ /* -4 and -6 mean failure of setuid/gid respectively. |
|
| 244 |
+ There is no point for us to continue if those failed. */ |
|
| 245 |
+ msg(M_ERR, "capng_change_id('%s','%s') failed: %d",
|
|
| 246 |
+ user_state->username, group_state->groupname, res); |
|
| 247 |
+ } |
|
| 248 |
+ else if (res == -3) |
|
| 249 |
+ {
|
|
| 250 |
+ msg(M_NONFATAL | M_ERRNO, "capng_change_id() failed applying capabilities"); |
|
| 251 |
+ msg(err_flags, "NOTE: previous error likely due to missing capability CAP_SETPCAP."); |
|
| 252 |
+ goto fallback; |
|
| 253 |
+ } |
|
| 254 |
+ else if (res < 0) |
|
| 255 |
+ {
|
|
| 256 |
+ msg(err_flags | M_ERRNO, "capng_change_id('%s','%s') failed retaining capabilities: %d",
|
|
| 257 |
+ user_state->username, group_state->groupname, res); |
|
| 258 |
+ goto fallback; |
|
| 259 |
+ } |
|
| 260 |
+ |
|
| 261 |
+ if (new_uid >= 0) |
|
| 262 |
+ {
|
|
| 263 |
+ msg(M_INFO, "UID set to %s", user_state->username); |
|
| 264 |
+ } |
|
| 265 |
+ if (new_gid >= 0) |
|
| 266 |
+ {
|
|
| 267 |
+ msg(M_INFO, "GID set to %s", group_state->groupname); |
|
| 268 |
+ } |
|
| 269 |
+ |
|
| 270 |
+ msg(M_INFO, "Capabilities retained: CAP_NET_ADMIN"); |
|
| 271 |
+ return; |
|
| 272 |
+ |
|
| 273 |
+fallback: |
|
| 274 |
+ /* capng_change_id() can leave this flag clobbered on failure |
|
| 275 |
+ * This is working around a bug in libcap-ng, which can leave the flag set |
|
| 276 |
+ * on failure: https://github.com/stevegrubb/libcap-ng/issues/33 */ |
|
| 277 |
+ if (prctl(PR_GET_KEEPCAPS) && prctl(PR_SET_KEEPCAPS, 0) < 0) |
|
| 278 |
+ {
|
|
| 279 |
+ msg(M_ERR, "Clearing KEEPCAPS flag failed"); |
|
| 280 |
+ } |
|
| 281 |
+#endif /* HAVE_LIBCAPNG */ |
|
| 282 |
+ |
|
| 283 |
+ if (keep_caps) |
|
| 284 |
+ {
|
|
| 285 |
+ msg(err_flags, "Unable to retain capabilities"); |
|
| 286 |
+ } |
|
| 287 |
+ |
|
| 288 |
+ platform_group_set(group_state); |
|
| 289 |
+ platform_user_set(user_state); |
|
| 290 |
+} |
|
| 291 |
+ |
|
| 158 | 292 |
/* Change process priority */ |
| 159 | 293 |
void |
| 160 | 294 |
platform_nice(int niceval) |
| ... | ... |
@@ -55,6 +55,9 @@ |
| 55 | 55 |
#include "basic.h" |
| 56 | 56 |
#include "buffer.h" |
| 57 | 57 |
|
| 58 |
+/* forward declared to avoid large amounts of extra includes */ |
|
| 59 |
+struct context; |
|
| 60 |
+ |
|
| 58 | 61 |
/* Get/Set UID of process */ |
| 59 | 62 |
|
| 60 | 63 |
struct platform_state_user {
|
| ... | ... |
@@ -79,11 +82,12 @@ struct platform_state_group {
|
| 79 | 79 |
|
| 80 | 80 |
bool platform_user_get(const char *username, struct platform_state_user *state); |
| 81 | 81 |
|
| 82 |
-void platform_user_set(const struct platform_state_user *state); |
|
| 83 |
- |
|
| 84 | 82 |
bool platform_group_get(const char *groupname, struct platform_state_group *state); |
| 85 | 83 |
|
| 86 |
-void platform_group_set(const struct platform_state_group *state); |
|
| 84 |
+void platform_user_group_set(const struct platform_state_user *user_state, |
|
| 85 |
+ const struct platform_state_group *group_state, |
|
| 86 |
+ struct context *c); |
|
| 87 |
+ |
|
| 87 | 88 |
|
| 88 | 89 |
/* |
| 89 | 90 |
* Extract UID or GID |