Use libcap to keep CAP_NET_RAW so that IP_TRANSPARENT works after setuid
I'd like to be able to use "transparent = source" and "setuid" to drop full root privileges.
It is possible to do this by keeping either the CAP_NET_ADMIN or CAP_NET_RAW capability.
This is based on https://github.com/dlundquist/sniproxy/commit/0ab83f8f45f73ba18ea5f84ccccd9e...
The capability will not be inherited if "exec" is used:
Name: stunnel Uid: 996 996 996 996 Gid: 988 988 988 988 ... CapInh: 0000000000000000 CapPrm: 0000000000002000 CapEff: 0000000000002000 --- It would be possible to keep CAP_NET_BIND_SERVICE too, to allow reloading a configuration that binds to ports below 1024. I don't think stunnel should keep this capability unless a service needs it at startup.
CREDITS.md | 25 ++++++++++ configure.ac | 33 ++++++++++++++ doc/stunnel.8.in | 2 +- doc/stunnel.html.in | 2 +- doc/stunnel.pod.in | 2 +- src/stunnel.c | 109 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+), 3 deletions(-)
diff --git a/CREDITS.md b/CREDITS.md index 6567392..5f12f92 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -27,6 +27,31 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+libcap usage in version 5.57 (from sniproxy): +Copyright 2017 Dustin Lundquist dustin@null-ptr.net +Copyright 2020 Simon Arlott + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + Several bugfixes and improvements mostly in versions 3.xx: * Brian Hatch bri@stunnel.org
diff --git a/configure.ac b/configure.ac index 9c2b2b6..f454710 100644 --- a/configure.ac +++ b/configure.ac @@ -352,6 +352,39 @@ AC_ARG_ENABLE(systemd, ] )
+# Disable POSIX capabilities support +AC_MSG_CHECKING([whether to enable POSIX capabilities support]) +AC_ARG_ENABLE(libcap, +[ --disable-libcap disable POSIX capabilities support], + [ + case "$enableval" in + yes) AC_MSG_RESULT([yes]) + AC_SEARCH_LIBS([cap_get_proc], [cap]) + AC_DEFINE([USE_LIBCAP], [1], + [Define to 1 to enable POSIX capabilities]) + ;; + no) AC_MSG_RESULT([no]) + ;; + *) AC_MSG_RESULT([error]) + AC_MSG_ERROR([Bad value "${enableval}"]) + ;; + esac + ], + [ + AC_MSG_RESULT([autodetecting]) + AC_SEARCH_LIBS([cap_get_proc], [cap], + [ AC_CHECK_HEADERS([sys/capability.h], [ + AC_DEFINE([USE_LIBCAP], [1], + [Define to 1 to enable POSIX capabilities]) + AC_MSG_NOTICE([libcap support enabled]) + ], [ + AC_MSG_NOTICE([libcap header not found]) + ]) ], [ + AC_MSG_NOTICE([libcap library not found]) + ]) + ] +) + # Disable use of libwrap (TCP wrappers) # it should be the last check! AC_MSG_CHECKING([whether to enable TCP wrappers support]) diff --git a/doc/stunnel.8.in b/doc/stunnel.8.in index b3df016..4b121bc 100644 --- a/doc/stunnel.8.in +++ b/doc/stunnel.8.in @@ -1251,7 +1251,7 @@ setuid .RS 4 .Sp The use of the 'setuid' option will also prevent \fBstunnel\fR from binding to privileged -(<1024) ports during configuration reloading. +(<1024) ports during configuration reloading or enabling of 'transparent = source'. .Sp When the 'chroot' option is used, \fBstunnel\fR will look for all its files (including the configuration file, certificates, the log file and the pid file) within the chroot diff --git a/doc/stunnel.html.in b/doc/stunnel.html.in index ee9e5d9..95a1b51 100644 --- a/doc/stunnel.html.in +++ b/doc/stunnel.html.in @@ -1506,7 +1506,7 @@ </li> </ul>
-<p>The use of the 'setuid' option will also prevent <b>stunnel</b> from binding to privileged (<1024) ports during configuration reloading.</p> +<p>The use of the 'setuid' option will also prevent <b>stunnel</b> from binding to privileged (<1024) ports during configuration reloading or enabling of 'transparent = source'.</p>
<p>When the 'chroot' option is used, <b>stunnel</b> will look for all its files (including the configuration file, certificates, the log file and the pid file) within the chroot jail.</p>
diff --git a/doc/stunnel.pod.in b/doc/stunnel.pod.in index e9abd4f..8f14fd9 100644 --- a/doc/stunnel.pod.in +++ b/doc/stunnel.pod.in @@ -1360,7 +1360,7 @@ setuid =back
The use of the 'setuid' option will also prevent B<stunnel> from binding to privileged -(<1024) ports during configuration reloading. +(<1024) ports during configuration reloading or enabling of 'transparent = source'.
When the 'chroot' option is used, B<stunnel> will look for all its files (including the configuration file, certificates, the log file and the pid file) within the chroot diff --git a/src/stunnel.c b/src/stunnel.c index 8121e0c..b84ce72 100644 --- a/src/stunnel.c +++ b/src/stunnel.c @@ -58,6 +58,11 @@
#endif /* USE_WIN32 */
+#ifdef USE_LIBCAP +#include <sys/prctl.h> +#include <sys/capability.h> +#endif + /**************************************** prototypes */
#ifdef __INNOTEK_LIBC__ @@ -191,6 +196,59 @@ int drop_privileges(int critical) { #ifdef HAVE_SETGROUPS gid_t gr_list[1]; #endif +#ifdef USE_LIBCAP + SERVICE_OPTIONS *opt; + int need_caps = 0; + cap_t caps; + cap_value_t cap_list[1]; + int keepcaps = 0; +#endif + +#ifdef USE_LIBCAP + for (opt = service_options.next; opt; opt = opt->next) { + if (opt->option.transparent_src) { /* transparent_src is enabled for this service */ + need_caps = 1; + break; + } + } + + /* add permitted flag to capability needed for IP_TRANSPARENT */ + if (CAP_IS_SUPPORTED(CAP_NET_RAW) && need_caps) { + const char *cap_failed = NULL; + + caps = cap_get_proc(); + if (caps != NULL) { + cap_list[0] = CAP_NET_RAW; + if (cap_set_flag(caps, CAP_PERMITTED, 1, cap_list, CAP_SET) == -1) { + cap_failed = "cap_set_flags[before]"; + goto cap_cleanup_before; + } + + if (cap_set_proc(caps) == -1) { + cap_failed = "cap_set_proc[before]"; + goto cap_cleanup_before; + } + + /* keep capabilities after setuid */ + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1) { + cap_failed = "prctl[before]"; + goto cap_cleanup_before; + } else { + keepcaps = 1; + } + +cap_cleanup_before: + cap_free(caps); + if (cap_failed != NULL && critical) { + sockerror(cap_failed); + return 1; + } + } else if (critical) { + sockerror("cap_get_proc[before]"); + return 1; + } + } +#endif
/* set uid and gid */ if(service_options.gid) { @@ -212,6 +270,57 @@ int drop_privileges(int critical) { return 1; } } + +#ifdef USE_LIBCAP + /* enable capability needed for IP_TRANSPARENT */ + if (CAP_IS_SUPPORTED(CAP_NET_RAW) && need_caps) { + const char *cap_failed = NULL; + + caps = cap_get_proc(); + if (caps != NULL) { + /* remove every capability from the list */ + cap_clear(caps); + cap_list[0] = CAP_NET_RAW; + + /* take back capability */ + if (cap_set_flag(caps, CAP_PERMITTED, 1, cap_list, CAP_SET) == -1) { + cap_failed = "cap_set_flag[after]"; + goto cap_cleanup_after; + } + + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, cap_list, CAP_SET) == -1) { + cap_failed = "cap_set_flag[after]"; + goto cap_cleanup_after; + } + + if (cap_set_proc(caps) == -1) { + cap_failed = "cap_set_proc[after]"; + goto cap_cleanup_after; + } + /* from now on it the new capability list is active, + * no other capabilities may be enabled */ + +cap_cleanup_after: + cap_free(caps); + if (cap_failed != NULL && critical) { + sockerror(cap_failed); + return 1; + } + } else if (critical) { + sockerror("cap_get_proc[after]"); + return 1; + } + } + + /* disable keeping capabilities across setuid */ + if (keepcaps && prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0) == -1) { + if (critical) { + sockerror("prctl"); + return 1; + } + } +#endif + #endif /* standard Unix */ return 0; }