Hi everybody!
OpenVPN already has support for dropping privileges and confining itself to a directory *after* startup (thanks to calls like setgid, setuid and chroot) which makes for much better management than if you had to respectively start OpenVPN unprivileged and add the privileges it (only) needs during startup, or if you chroot'ed OpenVPN before startup and had to copy the necessary files in the confinement directory. The very same problem occurs with SELinux policy enforcement: one can apply a policy before startup (such as the ready-to-use one provided by http://oss.tresys.com/projects/refpolicy/browser/trunk/policy/modules/services/openvpn.te) but it is waaaay too complicated, just because many rights have to be granted to OpenVPN during its initialization... or one can apply a very simple SELinux policy to OpenVPN when the only need left is basically network I/O operations. The only downside to this is that, just like with setuid() and chroot(), OpenVPN has to call setcon() itself, which is why I am submitting this patch :-) Please note that, while this patch does indeed bring a new dependency to libselinux in OpenVPN on Linux: * the feature will of course only be added if detected by ./configure * libselinux is so common now that even /bin/ls is linked against it on most Linux systems so OpenVPN should get SELinux support quite transparently ;-) Best regards, -- Sebastien Raveau Information Warfare Consultant http://blog.sebastien.raveau.name/
diff -urN -X generated openvpn-2.0.9.orig/configure.ac openvpn-2.0.9/configure.ac --- openvpn-2.0.9.orig/configure.ac 2006-10-01 14:18:15.000000000 +0200 +++ openvpn-2.0.9/configure.ac 2009-06-22 22:42:30.000000000 +0200 @@ -95,6 +95,12 @@ [DEBUG="yes"] ) +AC_ARG_ENABLE(selinux, + [ --disable-selinux Disable SELinux support], + [SELINUX="$enableval"], + [SELINUX="yes"] +) + AC_ARG_ENABLE(small, [ --enable-small Enable smaller executable size (disable OCC, usage message, and verb 4 parm list)], [SMALL="$enableval"], @@ -568,6 +574,23 @@ fi fi +dnl +dnl check for SELinux library and headers +dnl +if test "$SELINUX" = "yes"; then + AC_CHECKING([for libselinux Library and Header files]) + AC_CHECK_HEADER(selinux/selinux.h, + [AC_CHECK_LIB(selinux, setcon, + [ + OPENVPN_ADD_LIBS(-lselinux) + AC_DEFINE(HAVE_SETCON, 1, [SELinux support]) + ], + [AC_MSG_RESULT([SELinux library not found.])] + )], + [AC_MSG_RESULT([SELinux headers not found.])] + ) +fi + dnl enable multi-client mode if test "$MULTI" = "yes"; then AC_DEFINE(ENABLE_CLIENT_SERVER, 1, [Enable client/server capability]) diff -urN -X generated openvpn-2.0.9.orig/init.c openvpn-2.0.9/init.c --- openvpn-2.0.9.orig/init.c 2006-04-05 08:42:32.000000000 +0200 +++ openvpn-2.0.9/init.c 2009-06-22 22:42:30.000000000 +0200 @@ -405,6 +405,17 @@ { msg (M_INFO, "NOTE: UID/GID downgrade %s", why_not); } + + /* set SELinux context; doing it after chroot requires /proc + to be mounted in the chroot (which is annoying indeed but + doing it before requires more complex SELinux policies */ + if (c->options.selinux_context) + { + if (no_delay) + do_setcon(c->options.selinux_context); + else + msg (M_INFO, "NOTE: setcon %s", why_not); + } } } @@ -1563,8 +1574,8 @@ if (o->ping_send_timeout && !o->ping_rec_timeout) msg (M_WARN, "WARNING: --ping should normally be used with --ping-restart or --ping-exit"); - if ((o->username || o->groupname || o->chroot_dir) && (!o->persist_tun || !o->persist_key)) - msg (M_WARN, "WARNING: you are using user/group/chroot without persist-key/persist-tun -- this may cause restarts to fail"); + if ((o->username || o->groupname || o->chroot_dir || o->selinux_context) && (!o->persist_tun || !o->persist_key)) + msg (M_WARN, "WARNING: you are using user/group/chroot/setcon without persist-key/persist-tun -- this may cause restarts to fail"); #if P2MP if (o->pull && o->ifconfig_local && c->first_time) diff -urN -X generated openvpn-2.0.9.orig/misc.c openvpn-2.0.9/misc.c --- openvpn-2.0.9.orig/misc.c 2005-11-05 08:04:22.000000000 +0100 +++ openvpn-2.0.9/misc.c 2009-06-22 23:00:36.000000000 +0200 @@ -62,6 +62,25 @@ } } +/* Apply SELinux context in order to restrict what + OpenVPN can do to _only_ what it is supposed to + do after initialization is complete (basically + network I/O operations) */ +void +do_setcon (const char *context) +{ + if (context) + { +#ifdef HAVE_SETCON + if (-1 == setcon (context)) + msg (M_ERR, "setcon to '%s' failed; is /proc accessible?", context); + msg (M_INFO, "setcon to '%s' succeeded", context); +#else + msg (M_FATAL, "Sorry but I can't set SELinux context to '%s' because this operating system doesn't appear to support the setcon() system call", context); +#endif + } +} + /* Get/Set UID of process */ bool diff -urN -X generated openvpn-2.0.9.orig/openvpn.8 openvpn-2.0.9/openvpn.8 --- openvpn-2.0.9.orig/openvpn.8 2005-11-03 02:16:43.000000000 +0100 +++ openvpn-2.0.9/openvpn.8 2009-06-22 22:42:30.000000000 +0200 @@ -232,6 +232,7 @@ [\ \fB\-\-server\-bridge\fR\ \fIgateway\ netmask\ pool\-start\-IP\ pool\-end\-IP\fR\ ] [\ \fB\-\-server\fR\ \fInetwork\ netmask\fR\ ] [\ \fB\-\-service\fR\ \fIexit\-event\ [0|1]\fR\ ] +[\ \fB\-\-setcon\fR\ \fIcontext\fR\ ] [\ \fB\-\-setenv\fR\ \fIname\ value\fR\ ] [\ \fB\-\-shaper\fR\ \fIn\fR\ ] [\ \fB\-\-show\-adapters\fR\ ] @@ -1759,6 +1760,39 @@ are executed after the chroot operation. .\"********************************************************* .TP +.B --setcon context +Apply SELinux +.B context +after initialization. This +essentially provides the ability to restrict OpenVPN's +rights to only network I/O operations, thanks to +SELinux. This goes further than +.B --user +and +.B --chroot +in that those two, while being great security features, +unfortunately do not protect against privilege escalation +by exploitation of a vulnerable system call. You can of +course combine all three, but please note that since +setcon requires access to /proc you will have to provide +it inside the chroot directory (e.g. with mount --bind). + +Since the setcon operation is delayed until after +initialization, OpenVPN can be restricted to just +network-related system calls, whereas by applying the +context before startup (such as the OpenVPN one provided +in the SELinux Reference Policies) you will have to +allow many things required only during initialization. + +Like with chroot, complications can result when scripts +or restarts are executed after the setcon operation, +which is why you should really consider using the +.B --persist-key +and +.B --persist-tun +options. +.\"********************************************************* +.TP .B --daemon [progname] Become a daemon after all initialization functions are completed. This option will cause all message and error output to diff -urN -X generated openvpn-2.0.9.orig/options.c openvpn-2.0.9/options.c --- openvpn-2.0.9.orig/options.c 2005-12-13 00:50:43.000000000 +0100 +++ openvpn-2.0.9/options.c 2009-06-22 22:42:30.000000000 +0200 @@ -220,6 +220,7 @@ " caused by --ping-restart or SIGUSR1\n" "--user user : Set UID to user after initialization.\n" "--group group : Set GID to group after initialization.\n" + "--setcon context: Apply this SELinux context after initialization.\n" "--chroot dir : Chroot to this directory after initialization.\n" "--cd dir : Change to this directory before initialization.\n" "--daemon [name] : Become a daemon after initialization.\n" @@ -1016,6 +1017,7 @@ SHOW_STR (groupname); SHOW_STR (chroot_dir); SHOW_STR (cd_dir); + SHOW_STR (selinux_context); SHOW_STR (writepid); SHOW_STR (up_script); SHOW_STR (down_script); @@ -2919,6 +2921,12 @@ } options->cd_dir = p[1]; } + else if (streq (p[0], "setcon") && p[1]) + { + ++i; + VERIFY_PERMISSION (OPT_P_GENERAL); + options->selinux_context = p[1]; + } else if (streq (p[0], "writepid") && p[1]) { ++i; diff -urN -X generated openvpn-2.0.9.orig/options.h openvpn-2.0.9/options.h --- openvpn-2.0.9.orig/options.h 2005-11-01 12:06:11.000000000 +0100 +++ openvpn-2.0.9/options.h 2009-06-22 22:42:30.000000000 +0200 @@ -192,6 +192,7 @@ const char *groupname; const char *chroot_dir; const char *cd_dir; + const char *selinux_context; const char *writepid; const char *up_script; const char *down_script;