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.
This commit is contained in:
Ulrich Drepper 2004-10-03 19:33:48 +00:00
parent fc03df7aa6
commit 4401d75905
7 changed files with 286 additions and 14 deletions

View File

@ -1,5 +1,25 @@
2004-10-03 Ulrich Drepper <drepper@redhat.com>
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]

View File

@ -18,6 +18,7 @@
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */
#include <alloca.h>
#include <assert.h>
#include <atomic.h>
#include <error.h>
@ -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. */

View File

@ -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;
}

View File

@ -12,6 +12,8 @@
# server-user is ignored if nscd is started with -S parameters
# stat-user <user who is allowed to request statistics>
# reload-count unlimited|<number>
# paranoia <yes|no>
# restart-interval <time in seconds>
#
# enable-cache <service> <yes|no>
# positive-time-to-live <service> <time in seconds>
@ -31,6 +33,8 @@
# stat-user somebody
debug-level 0
# reload-count 5
paranoia no
# restart-interval 3600
enable-cache passwd yes
positive-time-to-live passwd 600

View File

@ -50,6 +50,10 @@ typedef enum
#define DEFAULT_RELOAD_LIMIT 5
/* Time before restarting the process in paranoia mode. */
#define RESTART_INTERVAL (60 * 60)
/* Structure describing dynamic part of one database. */
struct database_dyn
{
@ -127,6 +131,19 @@ extern unsigned int reload_count;
/* Pagesize minus one. */
extern uintptr_t pagesize_m1;
/* Nonzero if paranoia mode is enabled. */
extern int paranoia;
/* Time after which the process restarts. */
extern time_t restart_time;
/* How much time between restarts. */
extern time_t restart_interval;
/* Old current working directory. */
extern const char *oldcwd;
/* Old user and group ID. */
extern uid_t old_uid;
extern gid_t old_gid;
/* Prototypes for global functions. */
/* nscd.c */

View File

@ -18,13 +18,15 @@
02111-1307 USA. */
#include <ctype.h>
#include <errno.h>
#include <libintl.h>
#include <malloc.h>
#include <pwd.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
#include <libintl.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>
@ -191,7 +193,7 @@ nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb])
}
else if (strcmp (entry, "stat-user") == 0)
{
if (!arg1)
if (arg1 == NULL)
dbg_log (_("Must specify user name for stat-user option"));
else
{
@ -245,11 +247,41 @@ nscd_parse_file (const char *fname, struct database_dyn dbs[lastdb])
dbg_log (_("invalid value for 'reload-count': %u"), count);
}
}
else if (strcmp (entry, "paranoia") == 0)
{
if (strcmp (arg1, "no") == 0)
paranoia = 0;
else if (strcmp (arg1, "yes") == 0)
paranoia = 1;
}
else if (strcmp (entry, "restart-interval") == 0)
{
if (arg1 != NULL)
restart_interval = atol (arg1);
else
dbg_log (_("Must specify value for restart-interval option"));
}
else
dbg_log (_("Unknown option: %s %s %s"), entry, arg1, arg2);
}
while (!feof_unlocked (fp));
if (paranoia)
{
restart_time = time (NULL) + restart_interval;
/* Save the old current workding directory if we are in paranoia
mode. We have to change back to it. */
oldcwd = get_current_dir_name ();
if (oldcwd == NULL)
{
dbg_log (_("\
cannot get current working directory: %s; disabling paranoia mode"),
strerror (errno));
paranoia = 0;
}
}
/* Free the buffer. */
free (line);
/* Close configuration file. */

View File

@ -143,6 +143,8 @@ receive_print_stats (void)
int fd;
int i;
uid_t uid = getuid ();
const char *yesstr = _(" yes");
const char *nostr = _(" no");
/* Find out whether there is another user but root allowed to
request statistics. */
@ -223,8 +225,11 @@ receive_print_stats (void)
else
printf (_(" %2lus server runtime\n"), diff);
printf (_("%15lu number of times clients had to wait\n"),
data.client_queued);
printf (_("%15lu number of times clients had to wait\n"
"%15s paranoia mode enabled\n"
"%15lu restart internal\n"),
data.client_queued, paranoia ? yesstr : nostr,
(unsigned long int) restart_interval);
for (i = 0; i < lastdb; ++i)
{
@ -241,13 +246,13 @@ receive_print_stats (void)
/* The locale does not provide this information so we have to
translate it ourself. Since we should avoid short translation
terms we artifically increase the length. */
enabled = data.dbs[i].enabled ? _(" yes") : _(" no");
enabled = data.dbs[i].enabled ? yesstr : nostr;
if (check_file[0] == '\0')
check_file = data.dbs[i].check_file ? _(" yes") : _(" no");
check_file = data.dbs[i].check_file ? yesstr : nostr;
if (shared[0] == '\0')
shared = data.dbs[i].shared ? _(" yes") : _(" no");
shared = data.dbs[i].shared ? yesstr : nostr;
if (persistent[0] == '\0')
persistent = data.dbs[i].persistent ? _(" yes") : _(" no");
persistent = data.dbs[i].persistent ? yesstr : nostr;
if (all == 0)
/* If nothing happened so far report a 0% hit rate. */