From 4401d759051714fcc016a146685f3c13bed49442 Mon Sep 17 00:00:00 2001 From: Ulrich Drepper Date: Sun, 3 Oct 2004 19:33:48 +0000 Subject: Update. Implement paranoia mode. * nscd/connections.c (nscd_init): Mark database and socket descriptors as close on exec. (restart): New function. (restart_p): New function. (nscd_run): Add missing descrement of nready in case readylist is empty. (main_loop_poll): Call restart_p and restart. (main_loop_epoll): Likewise. (begin_drop_privileges): Save original UID and GID. * nscd/nscd.c: Define new variables paranoia, restart_time, restart_interval, oldcwd, old_gid, old_uid. (main): Disable paranoia mode if we are not forking. (check_pid): When re-execing, the PID file contains the same PID as the current process. Do not fail in this case. * nscd/nscd.conf: Add paranoia and restart-interval entries. * nscd/nscd.h: Define RESTART_INTERVAL. Declare new variables. * nscd/nscd_conf.c: Parse paranoia and restart-internal configurations. * nscd/nscd_stat.c: Print paranoia and restart-internal values. --- ChangeLog | 20 ++++++ nscd/connections.c | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++-- nscd/nscd.c | 17 ++++- nscd/nscd.conf | 4 ++ nscd/nscd.h | 17 +++++ nscd/nscd_conf.c | 36 +++++++++- nscd/nscd_stat.c | 17 +++-- 7 files changed, 286 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index 258cd77f4c..6ecb996c9d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,25 @@ 2004-10-03 Ulrich Drepper + Implement paranoia mode. + * nscd/connections.c (nscd_init): Mark database and socket descriptors + as close on exec. + (restart): New function. + (restart_p): New function. + (nscd_run): Add missing descrement of nready in case readylist is + empty. + (main_loop_poll): Call restart_p and restart. + (main_loop_epoll): Likewise. + (begin_drop_privileges): Save original UID and GID. + * nscd/nscd.c: Define new variables paranoia, restart_time, + restart_interval, oldcwd, old_gid, old_uid. + (main): Disable paranoia mode if we are not forking. + (check_pid): When re-execing, the PID file contains the same PID as + the current process. Do not fail in this case. + * nscd/nscd.conf: Add paranoia and restart-interval entries. + * nscd/nscd.h: Define RESTART_INTERVAL. Declare new variables. + * nscd/nscd_conf.c: Parse paranoia and restart-internal configurations. + * nscd/nscd_stat.c: Print paranoia and restart-internal values. + * nscd/connections.c: Implement alternative loop for main thread which uses epoll. * sysdeps/unix/sysv/linux/Makefile [subdir=nscd] diff --git a/nscd/connections.c b/nscd/connections.c index 8b167aab71..ace69fb455 100644 --- a/nscd/connections.c +++ b/nscd/connections.c @@ -18,6 +18,7 @@ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ +#include #include #include #include @@ -437,6 +438,18 @@ cannot create read-only descriptor for \"%s\"; no mmap"), } } + if (paranoia + && ((dbs[cnt].wr_fd != -1 + && fcntl (dbs[cnt].wr_fd, F_SETFD, FD_CLOEXEC) == -1) + || (dbs[cnt].ro_fd != -1 + && fcntl (dbs[cnt].ro_fd, F_SETFD, FD_CLOEXEC) == -1))) + { + dbg_log (_("\ +cannot set socket to close on exec: %s; disabling paranoia mode"), + strerror (errno)); + paranoia = 0; + } + if (dbs[cnt].head == NULL) { /* We do not use the persistent database. Just @@ -493,11 +506,22 @@ cannot create read-only descriptor for \"%s\"; no mmap"), exit (1); } - /* We don't wait for data otherwise races between threads can get - them stuck on accept. */ + /* We don't want to get stuck on accept. */ int fl = fcntl (sock, F_GETFL); - if (fl != -1) - fcntl (sock, F_SETFL, fl | O_NONBLOCK); + if (fl == -1 || fcntl (sock, F_SETFL, fl | O_NONBLOCK) == -1) + { + dbg_log (_("cannot change socket to nonblocking mode: %s"), + strerror (errno)); + exit (1); + } + + /* The descriptor needs to be closed on exec. */ + if (paranoia && fcntl (sock, F_SETFD, FD_CLOEXEC) == -1) + { + dbg_log (_("cannot set socket to close on exec: %s"), + strerror (errno)); + exit (1); + } /* Set permissions for the socket. */ chmod (_PATH_NSCDSOCKET, DEFFILEMODE); @@ -788,6 +812,138 @@ cannot handle old request version %d; current version is %d"), } +/* Restart the process. */ +static void +restart (void) +{ + /* First determine the parameters. We do not use the parameters + passed to main() since in case nscd is started by running the + dynamic linker this will not work. Yes, this is not the usual + case but nscd is part of glibc and we occasionally do this. */ + size_t buflen = 1024; + char *buf = alloca (buflen); + size_t readlen = 0; + int fd = open ("/proc/self/cmdline", O_RDONLY); + if (fd == -1) + { + dbg_log (_("\ +cannot open /proc/self/cmdline: %s; disabling paranoia mode"), + strerror (errno)); + + paranoia = 0; + return; + } + + while (1) + { + ssize_t n = TEMP_FAILURE_RETRY (read (fd, buf + readlen, + buflen - readlen)); + if (n == -1) + { + dbg_log (_("\ +cannot open /proc/self/cmdline: %s; disabling paranoia mode"), + strerror (errno)); + + close (fd); + paranoia = 0; + return; + } + + readlen += n; + + if (readlen < buflen) + break; + + /* We might have to extend the buffer. */ + size_t old_buflen = buflen; + char *newp = extend_alloca (buf, buflen, 2 * buflen); + buf = memmove (newp, buf, old_buflen); + } + + close (fd); + + /* Parse the command line. Worst case scenario: every two + characters form one parameter (one character plus NUL). */ + char **argv = alloca ((readlen / 2 + 1) * sizeof (argv[0])); + int argc = 0; + + char *cp = buf; + while (cp < buf + readlen) + { + argv[argc++] = cp; + cp = (char *) rawmemchr (cp, '\0') + 1; + } + argv[argc] = NULL; + + /* Second, change back to the old user if we changed it. */ + if (server_user != NULL) + { + if (setuid (old_uid) != 0) + { + dbg_log (_("\ +cannot change to old UID: %s; disabling paranoia mode"), + strerror (errno)); + + paranoia = 0; + return; + } + + if (setgid (old_gid) != 0) + { + dbg_log (_("\ +cannot change to old GID: %s; disabling paranoia mode"), + strerror (errno)); + + setuid (server_uid); + paranoia = 0; + return; + } + } + + /* Next change back to the old working directory. */ + if (chdir (oldcwd) == -1) + { + dbg_log (_("\ +cannot change to old working directory: %s; disabling paranoia mode"), + strerror (errno)); + + if (server_user != NULL) + { + setuid (server_uid); + setgid (server_gid); + } + paranoia = 0; + return; + } + + /* Synchronize memory. */ + for (int cnt = 0; cnt < lastdb; ++cnt) + { + /* Make sure nobody keeps using the database. */ + dbs[cnt].head->timestamp = 0; + + if (dbs[cnt].persistent) + // XXX async OK? + msync (dbs[cnt].head, dbs[cnt].memsize, MS_ASYNC); + } + + /* The preparations are done. */ + execv ("/proc/self/exe", argv); + + /* If we come here, we will never be able to re-exec. */ + dbg_log (_("re-exec failed: %s; disabling paranoia mode"), + strerror (errno)); + + if (server_user != NULL) + { + setuid (server_uid); + setgid (server_gid); + } + chdir ("/"); + paranoia = 0; +} + + /* List of file descriptors. */ struct fdlist { @@ -859,6 +1015,7 @@ nscd_run (void *p) just start pruning. */ if (readylist == NULL && to == ETIMEDOUT) { + --nready; pthread_mutex_unlock (&readylist_lock); goto only_prune; } @@ -1059,7 +1216,16 @@ fd_ready (int fd) } -/* Time a connection was accepted. */ +/* Check whether restarting should happen. */ +static inline int +restart_p (time_t now) +{ + return (paranoia && readylist == NULL && nready == nthreads + && now >= restart_time); +} + + +/* Array for times a connection was accepted. */ static time_t *starttime; @@ -1160,6 +1326,9 @@ main_loop_poll (void) while (conns[nused - 1].fd == -1); } } + + if (restart_p (now)) + restart (); } } @@ -1252,6 +1421,9 @@ main_loop_epoll (int efd) } else if (cnt != sock && starttime[cnt] == 0 && cnt == highest) --highest; + + if (restart_p (now)) + restart (); } } #endif @@ -1347,6 +1519,13 @@ begin_drop_privileges (void) server_uid = pwd->pw_uid; server_gid = pwd->pw_gid; + /* Save the old UID/GID if we have to change back. */ + if (paranoia) + { + old_uid = getuid (); + old_gid = getgid (); + } + if (getgrouplist (server_user, server_gid, NULL, &server_ngroups) == 0) { /* This really must never happen. */ diff --git a/nscd/nscd.c b/nscd/nscd.c index 146f61cb25..0ef54bcf08 100644 --- a/nscd/nscd.c +++ b/nscd/nscd.c @@ -79,6 +79,13 @@ time_t start_time; uintptr_t pagesize_m1; +int paranoia; +time_t restart_time; +time_t restart_interval = RESTART_INTERVAL; +const char *oldcwd; +uid_t old_uid; +gid_t old_gid; + static int check_pid (const char *file); static int write_pid (const char *file); @@ -248,6 +255,9 @@ main (int argc, char **argv) signal (SIGTTIN, SIG_IGN); signal (SIGTSTP, SIG_IGN); } + else + /* In foreground mode we are not paranoid. */ + paranoia = 0; /* Start the SELinux AVC. */ if (selinux_enabled) @@ -414,6 +424,7 @@ nscd_open_socket (void) return sock; } + /* Cleanup. */ void termination_handler (int signum) @@ -461,7 +472,11 @@ check_pid (const char *file) n = fscanf (fp, "%d", &pid); fclose (fp); - if (n != 1 || kill (pid, 0) == 0) + /* If we cannot parse the file default to assuming nscd runs. + If the PID is alive, assume it is running. That all unless + the PID is the same as the current process' since tha latter + can mean we re-exec. */ + if ((n != 1 || kill (pid, 0) == 0) && pid != getpid ()) return 1; } diff --git a/nscd/nscd.conf b/nscd/nscd.conf index f972851116..35f65a4a36 100644 --- a/nscd/nscd.conf +++ b/nscd/nscd.conf @@ -12,6 +12,8 @@ # server-user is ignored if nscd is started with -S parameters # stat-user # reload-count unlimited| +# paranoia +# restart-interval