From dfd9567cb9c0fd3e2d753b5a2b856155cd4ab006 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Levin" Date: Fri, 18 Sep 2009 16:32:38 +0000 Subject: [PATCH] Implement support of keeping readall capabilities after UID/GID switch Extend drop() function interface to accept 3rd parameter specifying whether process should keep capabilities required to read and search files and directories regardless of their access permissions. Introduce new bacula-fd option (-k) specifying that readall capabilities should be kept after UID/GID switch. The change moves drop() definition from bsys.c to new file priv.c, which is necessary to avoid linking every bacula executable with -lcap. If drop() would remain in bsys.c, then every executable which directly or indirectly uses other functions defined in bsys.c would have to be linked with -lcap, unless libbac is built as a shared library. While the change itself is portable, the implementation is Linux specific, it uses libcap to keep CAP_DAC_READ_SEARCH capability. If libcap is not available, or OS does not have sys/prctl.h, sys/capability.h, prctl(2), setreuid(2) and PR_SET_KEEPCAPS, then this change is almost noop. --- bacula/autoconf/configure.in | 12 +++ bacula/src/dird/Makefile.in | 5 +- bacula/src/dird/dird.c | 2 +- bacula/src/filed/Makefile.in | 5 +- bacula/src/filed/filed.c | 15 +++- bacula/src/lib/Makefile.in | 5 +- bacula/src/lib/bsys.c | 73 ------------------ bacula/src/lib/priv.c | 134 ++++++++++++++++++++++++++++++++++ bacula/src/lib/protos.h | 2 +- bacula/src/stored/Makefile.in | 5 +- bacula/src/stored/stored.c | 2 +- 11 files changed, 174 insertions(+), 86 deletions(-) create mode 100644 bacula/src/lib/priv.c diff --git a/bacula/autoconf/configure.in b/bacula/autoconf/configure.in index 0c449aa716..0b8149d7d7 100644 --- a/bacula/autoconf/configure.in +++ b/bacula/autoconf/configure.in @@ -2488,6 +2488,18 @@ AC_CHECK_LIB(pthread, pthread_create, PTHREAD_LIB="-lpthread", ] ) +dnl +dnl Check for headers, functions and libraries required to support +dnl keeping readall capabilities +dnl +AC_CHECK_HEADERS(sys/prctl.h sys/capability.h) +AC_CHECK_FUNCS(prctl setreuid) +AC_CHECK_LIB([cap], [cap_set_proc], [CAP_LIBS="-lcap"], [CAP_LIBS=]) +if test x$CAP_LIBS = x-lcap; then + AC_DEFINE(HAVE_LIBCAP, 1, [Define if you have libcap]) +fi +AC_SUBST(CAP_LIBS) + AC_SUBST(FDLIBS) AC_DEFINE(FDLIBS) diff --git a/bacula/src/dird/Makefile.in b/bacula/src/dird/Makefile.in index 076dea1ac8..0abab0e2a2 100644 --- a/bacula/src/dird/Makefile.in +++ b/bacula/src/dird/Makefile.in @@ -19,6 +19,7 @@ thisdir = src/dird DEBUG=@DEBUG@ GETTEXT_LIBS = @LIBINTL@ +CAP_LIBS = @CAP_LIBS@ PYTHON_LIBS = @PYTHON_LIBS@ PYTHON_INC = @PYTHON_INCDIR@ @@ -64,12 +65,12 @@ bacula-dir: Makefile $(SVROBJS) ../lib/libbac$(DEFAULT_ARCHIVE_TYPE) ../lib/libb @echo "Linking $@ ..." $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -L../lib -L../cats -L../findlib -o $@ $(SVROBJS) \ -lbacfind -lbacsql -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(DLIB) $(DB_LIBS) $(LIBS) \ - $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) + $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS) static-bacula-dir: Makefile $(SVROBJS) ../lib/libbac$(DEFAULT_ARCHIVE_TYPE) ../lib/libbaccfg$(DEFAULT_ARCHIVE_TYPE) ../lib/libbacpy$(DEFAULT_ARCHIVE_TYPE) ../cats/libbacsql$(DEFAULT_ARCHIVE_TYPE) ../findlib/libbacfind$(DEFAULT_ARCHIVE_TYPE) $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -static -L../lib -L../cats -L../findlib -o $@ $(SVROBJS) \ -lbacfind -lbacsql -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(DLIB) $(DB_LIBS) $(LIBS) \ - $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) + $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS) strip $@ Makefile: $(srcdir)/Makefile.in $(topdir)/config.status diff --git a/bacula/src/dird/dird.c b/bacula/src/dird/dird.c index 441a0eb298..f6f4225f07 100644 --- a/bacula/src/dird/dird.c +++ b/bacula/src/dird/dird.c @@ -270,7 +270,7 @@ int main (int argc, char *argv[]) load_dir_plugins(director->plugin_directory); - drop(uid, gid); /* reduce privileges if requested */ + drop(uid, gid, false); /* reduce privileges if requested */ /* If we are in testing mode, we don't try to fix the catalog */ cat_op mode=(test_config)?CHECK_CONNECTION:UPDATE_AND_FIX; diff --git a/bacula/src/filed/Makefile.in b/bacula/src/filed/Makefile.in index e19a727702..907e537bfc 100644 --- a/bacula/src/filed/Makefile.in +++ b/bacula/src/filed/Makefile.in @@ -37,6 +37,7 @@ SVROBJS = $(SVRSRCS:.c=.o) # these are the objects that are changed by the .configure process EXTRAOBJS = @OBJLIST@ +CAP_LIBS = @CAP_LIBS@ FDLIBS = @FDLIBS@ # extra libs for File daemon # extra items for linking on Win32 @@ -87,12 +88,12 @@ bacula-fd: Makefile $(SVROBJS) ../findlib/libbacfind$(DEFAULT_ARCHIVE_TYPE) ../ @echo "Linking $@ ..." $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -L../lib -L../findlib -o $@ $(SVROBJS) \ $(WIN32LIBS) $(FDLIBS) -lbacfind -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(LIBS) \ - $(DLIB) $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) + $(DLIB) $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS) static-bacula-fd: Makefile $(SVROBJS) ../findlib/libbacfind.a ../lib/libbacpy$(DEFAULT_ARCHIVE_TYPE) ../lib/libbaccfg$(DEFAULT_ARCHIVE_TYPE) ../lib/libbac$(DEFAULT_ARCHIVE_TYPE) @WIN32@ $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -static -L../lib -L../findlib -o $@ $(SVROBJS) \ $(WIN32LIBS) $(FDLIBS) -lbacfind -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(LIBS) \ - $(DLIB) $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) + $(DLIB) $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS) strip $@ Makefile: $(srcdir)/Makefile.in $(topdir)/config.status diff --git a/bacula/src/filed/filed.c b/bacula/src/filed/filed.c index 21b7c68fc3..d5e5ada948 100644 --- a/bacula/src/filed/filed.c +++ b/bacula/src/filed/filed.c @@ -82,6 +82,7 @@ PROG_COPYRIGHT " -dt print timestamp in debug output\n" " -f run in foreground (for debugging)\n" " -g groupid\n" +" -k keep readall capabilities\n" " -s no signals (for debugging)\n" " -t test configuration file and exit\n" " -u userid\n" @@ -105,6 +106,7 @@ int main (int argc, char *argv[]) { int ch; bool test_config = false; + bool keep_readall_caps = false; char *uid = NULL; char *gid = NULL; #ifdef HAVE_PYTHON @@ -121,7 +123,7 @@ int main (int argc, char *argv[]) init_msg(NULL, NULL); daemon_start_time = time(NULL); - while ((ch = getopt(argc, argv, "c:d:fg:stu:v?")) != -1) { + while ((ch = getopt(argc, argv, "c:d:fg:kstu:v?")) != -1) { switch (ch) { case 'c': /* configuration file */ if (configfile != NULL) { @@ -149,6 +151,10 @@ int main (int argc, char *argv[]) gid = optarg; break; + case 'k': + keep_readall_caps = true; + break; + case 's': no_signals = true; break; @@ -185,6 +191,11 @@ int main (int argc, char *argv[]) usage(); } + if (!uid && keep_readall_caps) { + Emsg0(M_ERROR, 0, _("-k option has no meaning without -u option.\n")); + exit(1); + } + server_tid = pthread_self(); if (!no_signals) { init_signals(terminate_filed); @@ -227,7 +238,7 @@ int main (int argc, char *argv[]) load_fd_plugins(me->plugin_directory); - drop(uid, gid); + drop(uid, gid, keep_readall_caps); #ifdef BOMB me += 1000000; diff --git a/bacula/src/lib/Makefile.in b/bacula/src/lib/Makefile.in index 9189d3eb4f..9d3b46a774 100644 --- a/bacula/src/lib/Makefile.in +++ b/bacula/src/lib/Makefile.in @@ -15,6 +15,7 @@ topdir = ../.. thisdir = src/lib DEBUG=@DEBUG@ +CAP_LIBS = @CAP_LIBS@ first_rule: all dummy: @@ -45,7 +46,7 @@ LIBBAC_SRCS = attr.c base64.c berrno.c bsys.c bget_msg.c \ cram-md5.c crc32.c crypto.c daemon.c edit.c fnmatch.c \ guid_to_name.c hmac.c jcr.c lex.c alist.c dlist.c \ md5.c message.c mem_pool.c openssl.c \ - plugins.c queue.c bregex.c \ + plugins.c priv.c queue.c bregex.c \ rwlock.c scan.c serial.c sha1.c \ signal.c smartall.c rblist.c tls.c tree.c \ util.c var.c watchdog.c workq.c btimers.c \ @@ -116,7 +117,7 @@ libbac.a: $(LIBBAC_OBJS) libbac.la: Makefile $(LIBBAC_OBJS) @echo "Making $@ ..." - $(LIBTOOL_LINK) $(CXX) $(DEFS) $(DEBUG) $(LDFLAGS) -o $@ $(LIBBAC_OBJS) -export-dynamic -rpath $(libdir) -version-info $(LIBBAC_LT_CURRENT):$(LIBBAC_LT_REVISION):$(LIBBAC_LT_AGE) $(WRAPLIBS) + $(LIBTOOL_LINK) $(CXX) $(DEFS) $(DEBUG) $(LDFLAGS) -o $@ $(LIBBAC_OBJS) -export-dynamic -rpath $(libdir) -version-info $(LIBBAC_LT_CURRENT):$(LIBBAC_LT_REVISION):$(LIBBAC_LT_AGE) $(WRAPLIBS) $(CAP_LIBS) libbaccfg.a: $(LIBBACCFG_OBJS) @echo "Making $@ ..." diff --git a/bacula/src/lib/bsys.c b/bacula/src/lib/bsys.c index 6626f645af..a23d041fa9 100644 --- a/bacula/src/lib/bsys.c +++ b/bacula/src/lib/bsys.c @@ -37,10 +37,6 @@ #include "bacula.h" -#ifdef HAVE_AIX_OS -extern "C" int initgroups(const char *,int); -#endif - static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t timer = PTHREAD_COND_INITIALIZER; @@ -578,75 +574,6 @@ bail_out: } -/* - * Drop to privilege new userid and new gid if non-NULL - */ -void drop(char *uname, char *gname) -{ -#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H) - struct passwd *passw = NULL; - struct group *group = NULL; - gid_t gid; - uid_t uid; - char username[1000]; - - Dmsg2(900, "uname=%s gname=%s\n", uname?uname:"NONE", gname?gname:"NONE"); - if (!uname && !gname) { - return; /* Nothing to do */ - } - - if (uname) { - if ((passw = getpwnam(uname)) == NULL) { - berrno be; - Emsg2(M_ERROR_TERM, 0, _("Could not find userid=%s: ERR=%s\n"), uname, - be.bstrerror()); - } - } else { - if ((passw = getpwuid(getuid())) == NULL) { - berrno be; - Emsg1(M_ERROR_TERM, 0, _("Could not find password entry. ERR=%s\n"), - be.bstrerror()); - } else { - uname = passw->pw_name; - } - } - /* Any OS uname pointer may get overwritten, so save name, uid, and gid */ - bstrncpy(username, uname, sizeof(username)); - uid = passw->pw_uid; - gid = passw->pw_gid; - if (gname) { - if ((group = getgrnam(gname)) == NULL) { - berrno be; - Emsg2(M_ERROR_TERM, 0, _("Could not find group=%s: ERR=%s\n"), gname, - be.bstrerror()); - } - gid = group->gr_gid; - } - if (initgroups(username, gid)) { - berrno be; - if (gname) { - Emsg3(M_ERROR_TERM, 0, _("Could not initgroups for group=%s, userid=%s: ERR=%s\n"), - gname, username, be.bstrerror()); - } else { - Emsg2(M_ERROR_TERM, 0, _("Could not initgroups for userid=%s: ERR=%s\n"), - username, be.bstrerror()); - } - } - if (gname) { - if (setgid(gid)) { - berrno be; - Emsg2(M_ERROR_TERM, 0, _("Could not set group=%s: ERR=%s\n"), gname, - be.bstrerror()); - } - } - if (setuid(uid)) { - berrno be; - Emsg1(M_ERROR_TERM, 0, _("Could not set specified userid: %s\n"), username); - } -#endif -} - - /* BSDI does not have this. This is a *poor* simulation */ #ifndef HAVE_STRTOLL long long int diff --git a/bacula/src/lib/priv.c b/bacula/src/lib/priv.c new file mode 100644 index 0000000000..82f416554e --- /dev/null +++ b/bacula/src/lib/priv.c @@ -0,0 +1,134 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2000-2009 Free Software Foundation Europe e.V. + + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can redistribute it and/or + modify it under the terms of version two of the GNU General Public + License as published by the Free Software Foundation and included + in the file LICENSE. + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + Bacula® is a registered trademark of Kern Sibbald. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, + Switzerland, email:ftf@fsfeurope.org. +*/ + +#include "bacula.h" + +#undef ENABLE_KEEP_READALL_CAPS_SUPPORT +#if defined(HAVE_SYS_PRCTL_H) && defined(HAVE_SYS_CAPABILITY_H) && \ + defined(HAVE_PRCTL) && defined(HAVE_SETREUID) && defined(HAVE_LIBCAP) +# include +# include +# if defined(PR_SET_KEEPCAPS) +# define ENABLE_KEEP_READALL_CAPS_SUPPORT +# endif +#endif + +#ifdef HAVE_AIX_OS +extern "C" int initgroups(const char *,int); +#endif + +/* + * Lower privileges by switching to new UID and GID if non-NULL. + * If requested, keep readall capabilities after switch. + */ +void drop(char *uname, char *gname, bool keep_readall_caps) +{ + struct passwd *passw = NULL; + struct group *group = NULL; + gid_t gid; + uid_t uid; + char username[1000]; + + Dmsg2(900, "uname=%s gname=%s\n", uname?uname:"NONE", gname?gname:"NONE"); + if (!uname && !gname) { + return; /* Nothing to do */ + } + + if (uname) { + if ((passw = getpwnam(uname)) == NULL) { + berrno be; + Emsg2(M_ERROR_TERM, 0, _("Could not find userid=%s: ERR=%s\n"), uname, + be.bstrerror()); + } + } else { + if ((passw = getpwuid(getuid())) == NULL) { + berrno be; + Emsg1(M_ERROR_TERM, 0, _("Could not find password entry. ERR=%s\n"), + be.bstrerror()); + } else { + uname = passw->pw_name; + } + } + /* Any OS uname pointer may get overwritten, so save name, uid, and gid */ + bstrncpy(username, uname, sizeof(username)); + uid = passw->pw_uid; + gid = passw->pw_gid; + if (gname) { + if ((group = getgrnam(gname)) == NULL) { + berrno be; + Emsg2(M_ERROR_TERM, 0, _("Could not find group=%s: ERR=%s\n"), gname, + be.bstrerror()); + } + gid = group->gr_gid; + } + if (initgroups(username, gid)) { + berrno be; + if (gname) { + Emsg3(M_ERROR_TERM, 0, _("Could not initgroups for group=%s, userid=%s: ERR=%s\n"), + gname, username, be.bstrerror()); + } else { + Emsg2(M_ERROR_TERM, 0, _("Could not initgroups for userid=%s: ERR=%s\n"), + username, be.bstrerror()); + } + } + if (gname) { + if (setgid(gid)) { + berrno be; + Emsg2(M_ERROR_TERM, 0, _("Could not set group=%s: ERR=%s\n"), gname, + be.bstrerror()); + } + } + if (keep_readall_caps) { +#ifdef ENABLE_KEEP_READALL_CAPS_SUPPORT + cap_t caps; + + if (prctl(PR_SET_KEEPCAPS, 1)) { + berrno be; + Emsg1(M_ERROR_TERM, 0, _("prctl failed: ERR=%s\n"), be.bstrerror()); + } + if (setreuid(uid, uid)) { + berrno be; + Emsg1(M_ERROR_TERM, 0, _("setreuid failed: ERR=%s\n"), be.bstrerror()); + } + if (!(caps = cap_from_text("cap_dac_read_search=ep"))) { + berrno be; + Emsg1(M_ERROR_TERM, 0, _("cap_from_text failed: ERR=%s\n"), be.bstrerror()); + } + if (cap_set_proc(caps) < 0) { + berrno be; + Emsg1(M_ERROR_TERM, 0, _("cap_set_proc failed: ERR=%s\n"), be.bstrerror()); + } + cap_free(caps); +#else + Emsg0(M_ERROR_TERM, 0, _("Keep readall capabilities is not implemented on this platform yet\n")); +#endif + } else if (setuid(uid)) { + berrno be; + Emsg1(M_ERROR_TERM, 0, _("Could not set specified userid: %s\n"), username); + } +} diff --git a/bacula/src/lib/protos.h b/bacula/src/lib/protos.h index 5379ec5e05..c5b7ce1fb2 100644 --- a/bacula/src/lib/protos.h +++ b/bacula/src/lib/protos.h @@ -69,7 +69,7 @@ int bvsnprintf (char *str, int32_t size, const char *format, v int pool_sprintf (char *pool_buf, const char *fmt, ...); void create_pid_file (char *dir, const char *progname, int port); int delete_pid_file (char *dir, const char *progname, int port); -void drop (char *uid, char *gid); +void drop (char *uid, char *gid, bool keep_readall_caps); int bmicrosleep (int32_t sec, int32_t usec); char *bfgets (char *s, int size, FILE *fd); void make_unique_filename (POOLMEM **name, int Id, char *what); diff --git a/bacula/src/stored/Makefile.in b/bacula/src/stored/Makefile.in index 80ccf46145..bd54b897bd 100644 --- a/bacula/src/stored/Makefile.in +++ b/bacula/src/stored/Makefile.in @@ -70,6 +70,7 @@ COPYOBJS = bcopy.o block.o device.o dev.o label.o vtape.o \ # these are the objects that are changed by the .configure process EXTRAOBJS = @OBJLIST@ +CAP_LIBS = @CAP_LIBS@ FDLIBS=@FDLIBS@ @@ -91,12 +92,12 @@ bacula-sd: Makefile $(SDOBJS) ../lib/libbacpy$(DEFAULT_ARCHIVE_TYPE) ../lib/libb @echo "Linking $@ ..." $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -L../lib -o $@ $(SDOBJS) $(FDLIBS) \ -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(DLIB) $(LIBS) $(WRAPLIBS) \ - $(GETTEXT_LIBS) $(OPENSSL_LIBS) + $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS) static-bacula-sd: Makefile $(SDOBJS) ../lib/libbacpy$(DEFAULT_ARCHIVE_TYPE) ../lib/libbaccfg$(DEFAULT_ARCHIVE_TYPE) ../lib/libbac$(DEFAULT_ARCHIVE_TYPE) $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -static -L../lib -o $@ $(SDOBJS) $(FDLIBS) \ -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(DLIB) $(LIBS) $(WRAPLIBS) \ - $(GETTEXT_LIBS) $(OPENSSL_LIBS) + $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS) strip $@ btape.o: btape.c diff --git a/bacula/src/stored/stored.c b/bacula/src/stored/stored.c index 76f7b2e2cd..3ee885bad8 100644 --- a/bacula/src/stored/stored.c +++ b/bacula/src/stored/stored.c @@ -261,7 +261,7 @@ int main (int argc, char *argv[]) load_sd_plugins(me->plugin_directory); - drop(uid, gid); + drop(uid, gid, false); cleanup_old_files(); -- 2.39.5