From db649ea4ba7f1446ec87e798a5cc723f9a415721 Mon Sep 17 00:00:00 2001 From: "Peter A. Bigot" Date: Sat, 31 May 2014 19:03:55 -0500 Subject: [PATCH] rrdcached: add options to drop privileges after daemonize Some init systems have an interface to run daemons as unprivileged accounts, but they conflict with rrdcached's expectations of creating pid files and sockets in privileged areas when following Filesystem Hierarchy Standard namings. Add options that are used to drop to unprivileged group and accounts after all activities that require privilege have been completed. Signed-off-by: Peter A. Bigot --- configure.ac | 4 +- doc/rrdcached.pod | 18 +++++++- src/rrd_daemon.c | 102 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 115 insertions(+), 9 deletions(-) diff --git a/configure.ac b/configure.ac index 1a3af821..eac4cfdb 100644 --- a/configure.ac +++ b/configure.ac @@ -199,7 +199,7 @@ CONFIGURE_PART(Checking for Header Files) dnl Checks for header files. AC_HEADER_STDC AC_HEADER_DIRENT -AC_CHECK_HEADERS(langinfo.h stdint.h inttypes.h libgen.h features.h sys/stat.h sys/types.h fcntl.h fp_class.h malloc.h unistd.h ieeefp.h math.h sys/times.h sys/param.h sys/resource.h signal.h float.h stdio.h stdlib.h errno.h string.h ctype.h) +AC_CHECK_HEADERS(langinfo.h stdint.h inttypes.h libgen.h features.h sys/stat.h sys/types.h fcntl.h fp_class.h malloc.h unistd.h ieeefp.h math.h sys/times.h sys/param.h sys/resource.h signal.h float.h stdio.h stdlib.h errno.h string.h ctype.h grp.h pwd.h) dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST @@ -227,7 +227,7 @@ AC_C_BIGENDIAN dnl for each function found we get a definition in config.h dnl of the form HAVE_FUNCTION -AC_CHECK_FUNCS(nl_langinfo tzset fsync mbstowcs opendir readdir chdir chroot getuid strerror snprintf vsnprintf vasprintf fpclass class fp_class isnan memmove strchr mktime getrusage gettimeofday) +AC_CHECK_FUNCS(nl_langinfo tzset fsync mbstowcs opendir readdir chdir chroot getgid getuid setgid setuid strerror snprintf vsnprintf vasprintf fpclass class fp_class isnan memmove strchr mktime getrusage gettimeofday getpwnam getgrnam) AC_FUNC_STRERROR_R diff --git a/doc/rrdcached.pod b/doc/rrdcached.pod index 8ac11a4c..9fb29613 100644 --- a/doc/rrdcached.pod +++ b/doc/rrdcached.pod @@ -11,6 +11,7 @@ B [B<-b>EIE[B<-B>]] [B<-F>] [B<-f>EI] +[B<-G>EI]] [B<-g>] [B<-j>EI] [B<-L>] @@ -22,6 +23,7 @@ B [B<-R>] [B<-s>EI] [B<-t>EI] +[B<-U>EI]] [B<-w>EI] [B<-z>EI] @@ -276,9 +278,23 @@ size. =item B<-O> -Preven the CREATE command from overwriting existing files, even when it is +Prevent the CREATE command from overwriting existing files, even when it is instructed to do so. This is for added security. +=item B<-G> -I + +When running as daemon and invoked from a privileged account, reset +group privileges to those of I. The group may be specified as +a name or as a group ID. The daemon will exit with a diagnostic if +it cannot successfully transition to the specified group. + +=item B<-U> -I + +When running as daemon and invoked from a privileged account, reset +user privileges to those of I. The user may be specified as +a name or as a user ID. The daemon will exit with a diagnostic if +it cannot successfully transition to the specified user. + =back =head1 AFFECTED RRDTOOL COMMANDS diff --git a/src/rrd_daemon.c b/src/rrd_daemon.c index 06e7da50..beaf30e8 100644 --- a/src/rrd_daemon.c +++ b/src/rrd_daemon.c @@ -102,6 +102,7 @@ #include #include #include +#include #ifdef HAVE_LIBWRAP #include @@ -220,6 +221,7 @@ typedef struct { */ static int stay_foreground = 0; static uid_t daemon_uid; +static gid_t daemon_gid; static listen_socket_t *listen_fds = NULL; static size_t listen_fds_num = 0; @@ -3313,8 +3315,6 @@ static int daemonize (void) /* {{{ */ int pid_fd; char *base_dir; - daemon_uid = geteuid(); - pid_fd = open_pidfile("create", O_CREAT|O_EXCL|O_WRONLY); if (pid_fd < 0) pid_fd = check_pidfile(); @@ -3373,7 +3373,6 @@ static int daemonize (void) /* {{{ */ openlog ("rrdcached", LOG_PID, LOG_DAEMON); RRDD_LOG(LOG_INFO, "starting up"); - install_signal_receiver(); cache_tree = g_tree_new_full ((GCompareDataFunc) strcmp, NULL, NULL, (GDestroyNotify) free_cache_item); @@ -3383,8 +3382,35 @@ static int daemonize (void) /* {{{ */ goto error; } - return write_pidfile (pid_fd); + if (0 == write_pidfile (pid_fd)) + { + /* Writing the pid file was the last act that might require privileges. + * Attempt to change to the desired runtime privilege level. */ + if (getegid() != daemon_gid) + { + if (0 != setgid(daemon_gid)) + { + RRDD_LOG (LOG_ERR, "daemonize: failed to setgid(%u)", daemon_gid); + goto error; + } + RRDD_LOG(LOG_INFO, "setgid(%u) succeeded", daemon_gid); + } + if (geteuid() != daemon_uid) + { + if (0 != setuid(daemon_uid)) + { + RRDD_LOG (LOG_ERR, "daemonize: failed to setuid(%u)", daemon_uid); + goto error; + } + RRDD_LOG(LOG_INFO, "setuid(%u) succeeded", daemon_uid); + } + /* Delay creation of threads until the final privilege level has + * been reached. */ + install_signal_receiver(); + return 0; + } + /*FALLTHRU*/ error: remove_pidfile(); return -1; @@ -3431,10 +3457,12 @@ static int read_options (int argc, char **argv) /* {{{ */ socket_permission_clear (&default_socket); + daemon_uid = geteuid(); + daemon_gid = getegid(); default_socket.socket_group = (gid_t)-1; default_socket.socket_permissions = (mode_t)-1; - while ((option = getopt(argc, argv, "?a:Bb:Ff:ghj:Ll:m:OP:p:Rs:t:w:z:")) != -1) + while ((option = getopt(argc, argv, "?a:Bb:Ff:gG:hj:Ll:m:OP:p:Rs:t:U:w:z:")) != -1) { switch (option) { @@ -3446,6 +3474,66 @@ static int read_options (int argc, char **argv) /* {{{ */ stay_foreground=1; break; + case 'G': +#if defined(HAVE_GETGRNAM) && defined(HAVE_GRP_H) && defined(HAVE_SETGID) + { + gid_t group_gid; + char * ep; + struct group *grp; + + group_gid = strtoul(optarg, &ep, 10); + if (0 == *ep) + { + /* we were passed a number */ + grp = getgrgid(group_gid); + } + else + { + grp = getgrnam(optarg); + } + if (NULL == grp) + { + fprintf (stderr, "read_options: couldn't map \"%s\" to a group, Sorry\n", optarg); + return (5); + } + daemon_gid = grp->gr_gid; + break; + } +#else + fprintf(stderr, "read_options: -G not supported.\n"); + return 5; +#endif + + case 'U': +#if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H) && defined(HAVE_SETUID) + { + uid_t uid; + char * ep; + struct passwd *pw; + + uid = strtoul(optarg, &ep, 10); + if (0 == *ep) + { + /* we were passed a number */ + pw = getpwuid(uid); + } + else + { + pw = getpwnam(optarg); + } + if (NULL == pw) + { + fprintf (stderr, "read_options: couldn't map \"%s\" to a user, Sorry\n", optarg); + return (5); + } + daemon_uid = pw->pw_uid; + break; + } +#else + fprintf(stderr, "read_options: -U not supported.\n"); + return 5; +#endif + case 'L': case 'l': { @@ -3618,7 +3706,7 @@ static int read_options (int argc, char **argv) /* {{{ */ } break; - case 'R': + case 'R': config_allow_recursive_mkdir = 1; break; @@ -3767,6 +3855,7 @@ static int read_options (int argc, char **argv) /* {{{ */ " -b Base directory to change to.\n" " -F Always flush all updates at shutdown\n" " -f Interval in which to flush dead data.\n" + " -G Unprivileged group used when running.\n" " -g Do not fork and run in the foreground.\n" " -j Directory in which to create the journal files.\n" " -L Open sockets on all INET interfaces using default port.\n" @@ -3784,6 +3873,7 @@ static int read_options (int argc, char **argv) /* {{{ */ " (the socket will also have read/write permissions " "for that group)\n" " -t Number of write threads.\n" + " -U Unprivileged user account used when running.\n" " -w Interval in which to write data.\n" " -z Delay writes up to seconds to spread load\n" "\n" -- 2.47.3