statd drops all capabilities except for CAP_NET_BIND when it starts. It's possible though that if it ever had a compromise that an attacker would be able to invoke a setuid process (or something with file capabilities) in order to reinstate some caps. This could happen as a result of the daemon becoming compromised, or possibly as a result of the ha-callout program becoming compromised. In order to prevent that, have statd also prune the capability bounding set to nothing prior to dropping capabilities. That ensures that the process won't be able to reacquire capabilities via any means -- including exec'ing a setuid program. We do however need to be cognizant of the fact that PR_CAPBSET_DROP was only added in 2.6.25, so check to make sure that #define exists via autoconf before we rely on it. In order to do that, we must add ax_check_define.m4 from the GNU autoconf macro archive. Furthermore, do a runtime check to see if /proc/sys/kernel/cap-bound exists before attempting to clear the bounding set. If it does, then don't bother trying since it won't work. In that event though, do throw a warning however since the presence of that file indicates that there is a disconnect between the build and runtime environments. Cc: Chuck Lever <chuck.lever@xxxxxxxxxx> Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx> --- aclocal/ax_check_define.m4 | 92 ++++++++++++++++++++++++++++++++++++++++++++ aclocal/libcap.m4 | 5 ++ support/nsm/file.c | 57 ++++++++++++++++++++++++++- 3 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 aclocal/ax_check_define.m4 diff --git a/aclocal/ax_check_define.m4 b/aclocal/ax_check_define.m4 new file mode 100644 index 0000000..4bc6948 --- /dev/null +++ b/aclocal/ax_check_define.m4 @@ -0,0 +1,92 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_check_define.html +# =========================================================================== +# +# SYNOPSIS +# +# AC_CHECK_DEFINE([symbol], [ACTION-IF-FOUND], [ACTION-IF-NOT]) +# AX_CHECK_DEFINE([includes],[symbol], [ACTION-IF-FOUND], [ACTION-IF-NOT]) +# +# DESCRIPTION +# +# Complements AC_CHECK_FUNC but it does not check for a function but for a +# define to exist. Consider a usage like: +# +# AC_CHECK_DEFINE(__STRICT_ANSI__, CFLAGS="$CFLAGS -D_XOPEN_SOURCE=500") +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim <guidod@xxxxxx> +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 8 + +AU_ALIAS([AC_CHECK_DEFINED], [AC_CHECK_DEFINE]) +AC_DEFUN([AC_CHECK_DEFINE],[ +AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$1])dnl +AC_CACHE_CHECK([for $1 defined], ac_var, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ + #ifdef $1 + int ok; + #else + choke me + #endif +]])],[AS_VAR_SET(ac_var, yes)],[AS_VAR_SET(ac_var, no)])) +AS_IF([test AS_VAR_GET(ac_var) != "no"], [$2], [$3])dnl +AS_VAR_POPDEF([ac_var])dnl +]) + +AU_ALIAS([AX_CHECK_DEFINED], [AX_CHECK_DEFINE]) +AC_DEFUN([AX_CHECK_DEFINE],[ +AS_VAR_PUSHDEF([ac_var],[ac_cv_defined_$2_$1])dnl +AC_CACHE_CHECK([for $2 defined in $1], ac_var, +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <$1>]], [[ + #ifdef $2 + int ok; + #else + choke me + #endif +]])],[AS_VAR_SET(ac_var, yes)],[AS_VAR_SET(ac_var, no)])) +AS_IF([test AS_VAR_GET(ac_var) != "no"], [$3], [$4])dnl +AS_VAR_POPDEF([ac_var])dnl +]) + +AC_DEFUN([AX_CHECK_FUNC], +[AS_VAR_PUSHDEF([ac_var], [ac_cv_func_$2])dnl +AC_CACHE_CHECK([for $2], ac_var, +dnl AC_LANG_FUNC_LINK_TRY +[AC_LINK_IFELSE([AC_LANG_PROGRAM([$1 + #undef $2 + char $2 ();],[ + char (*f) () = $2; + return f != $2; ])], + [AS_VAR_SET(ac_var, yes)], + [AS_VAR_SET(ac_var, no)])]) +AS_IF([test AS_VAR_GET(ac_var) = yes], [$3], [$4])dnl +AS_VAR_POPDEF([ac_var])dnl +])# AC_CHECK_FUNC diff --git a/aclocal/libcap.m4 b/aclocal/libcap.m4 index f8a0ed1..a310366 100644 --- a/aclocal/libcap.m4 +++ b/aclocal/libcap.m4 @@ -5,6 +5,11 @@ AC_DEFUN([AC_LIBCAP], [ dnl look for prctl AC_CHECK_FUNC([prctl], , AC_MSG_ERROR([prctl syscall is not available])) + dnl check to see if PR_CAPBSET_DROP is defined + AX_CHECK_DEFINE([linux/prctl.h], [PR_CAPBSET_DROP], + AC_DEFINE([HAVE_PR_CAPBSET_DROP], 1, [Define to 1 if you have the 'PR_CAPBSET_DROP' capability defined.]), + AC_MSG_WARN([PR_CAPBSET_DROP not defined! Unable to build in support for dropping the capabilities bounding set])) + AC_ARG_ENABLE([caps], [AS_HELP_STRING([--disable-caps], [Disable capabilities support])]) diff --git a/support/nsm/file.c b/support/nsm/file.c index 5dd52c1..25ed028 100644 --- a/support/nsm/file.c +++ b/support/nsm/file.c @@ -338,10 +338,10 @@ nsm_is_default_parentdir(void) * * Returns true if successful, or false if some error occurred. */ +#ifdef HAVE_SYS_CAPABILITY_H static _Bool nsm_clear_capabilities(void) { -#ifdef HAVE_SYS_CAPABILITY_H cap_t caps; caps = cap_from_text("cap_net_bind_service=ep"); @@ -357,10 +357,60 @@ nsm_clear_capabilities(void) } (void)cap_free(caps); -#endif return true; } +#define CAP_BOUND_PROCFILE "/proc/sys/kernel/cap-bound" +static _Bool +prune_bounding_set(void) +{ +#ifdef HAVE_PR_CAPBSET_DROP + int ret; + unsigned long i; + struct stat st; + + /* + * Prior to kernel 2.6.25, the capabilities bounding set was a global + * value. Check to see if /proc/sys/kernel/cap-bound exists and don't + * bother to clear the bounding set if it does. + */ + ret = stat(CAP_BOUND_PROCFILE, &st); + if (!ret) { + xlog(L_WARNING, "%s exists. Not attempting to clear " + "capabilities bounding set.", + CAP_BOUND_PROCFILE); + return true; + } else if (errno != ENOENT) { + /* Warn, but attempt to clear the bounding set anyway. */ + xlog(L_WARNING, "Unable to stat %s: %m", CAP_BOUND_PROCFILE); + } + + /* prune the bounding set to nothing */ + for (i = 0; i <= CAP_LAST_CAP; ++i) { + ret = prctl(PR_CAPBSET_DROP, i, 0, 0, 0); + if (ret) { + xlog(L_ERROR, "Unable to prune capability %lu from " + "bounding set: %m", i); + return false; + } + } +#endif /* HAVE_PR_CAPBSET_DROP */ + return true; +} +#else /* !HAVE_SYS_CAPABILITY_H */ +static _Bool +nsm_clear_capabilities(void) +{ + return true; +} + +static _Bool +prune_bounding_set(void) +{ + return true; +} +#endif /* HAVE_SYS_CAPABILITY_H */ + /** * nsm_drop_privileges - drop root privileges * @pidfd: file descriptor of a pid file @@ -393,6 +443,9 @@ nsm_drop_privileges(const int pidfd) return false; } + if (!prune_bounding_set()) + return false; + if (st.st_uid == 0) { xlog_warn("Running as root. " "chown %s to choose different user", nsm_base_dirname); -- 1.7.7.6 -- To unsubscribe from this list: send the line "unsubscribe linux-nfs" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html