Browse code

Linux: Retain CAP_NET_ADMIN when dropping privileges

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>

Timo Rothenpieler authored on 2022/05/14 19:37:17
Showing 6 changed files
... ...
@@ -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