Submitted By: Robert Connolly (ashes) Date: 2008-01-18 Initial Package Version: 1.5 Upstream Status: Submitted - Partially.. the Owl patches were submitted http://www.infodrom.org/projects/sysklogd/download/patches/ Origin: http://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/sysklogd/ sysklogd-1.4.2-caen-owl-klogd-drop-root.diff v1.2 sysklogd-1.4.2-caen-owl-syslogd-drop-root.diff v1.1 sysklogd-1.4.2-alt-syslogd-chroot.diff v1.2 and the '-P' option from sysklogd_1.4.1-16ubuntu6.diff Description: This patch adds the '-j' option to syslogd and klogd, so they can chroot into a jail. This patch also adds the '-u' option to syslogd and klogd, so they can change to the specified username before entering the jail. This patch also adds '-P' to klogd so it can use an alternative /proc/kmsg file, which is nescessary with Linux-2.6 and also works with Linux-2.4. To get klogd to drop privileges and work with Linux-2.6, root must make a second copy of /proc/kmsg because the kernel checks for the CAP_SYS_ADMIN capability before every read to /proc/kmsg. In Linux-2.4 the capability is only checked when /proc/kmsg is first opened. Set up syslogd and klogd as regular users in a chroot jail like this: groupadd syslogd groupadd klogd useradd -d /var/lib/syslogd -s /bin/false -g syslogd syslogd useradd -d /var/lib/klogd -s /bin/false -g klogd klogd install -d -m0000 /var/lib/syslogd install -d -m0000 /var/lib/klogd This is a snippet of my /etc/rc.d/init.d/sysklogd file: start) boot_mesg "Starting system log daemon..." loadproc /usr/sbin/syslogd -m 0 -u syslogd -j /var/lib/syslogd boot_mesg "Starting kernel log daemon..." /usr/bin/install -d -m0700 -o root -g root /var/run/klogd /usr/bin/mkfifo -m 700 /var/run/klogd/kmsg /bin/chown klogd:klogd /var/run/klogd/kmsg /bin/sh -c -- '/bin/dd bs=1 if=/proc/kmsg of=/var/run/klogd/kmsg &' loadproc /usr/sbin/klogd -x -u klogd -j /var/lib/klogd \ -P /var/run/klogd/kmsg ;; stop) boot_mesg "Stopping kernel log daemon..." killproc /usr/sbin/klogd /bin/fuser -s -k /var/run/klogd/kmsg /bin/rm -f /var/run/klogd/kmsg boot_mesg "Stopping system log daemon..." killproc /usr/sbin/syslogd ;; Remove the 'reload)' function, it will not work correctly after the daemons have dropped to regular users. For the same reason, do not use 'kill -HUP' with syslogd or klogd. The klogd daemon does not need to ever be root, but root privileges are needed to chroot the klogd daemon. If you prefer klogd can be started with '/bin/su', or the 'runas' program: http://www.ibiblio.org/pub/Linux/system/security/runas-1.01.tar.gz Also make sure the klogd user is able to read and execute /usr/sbin/klogd. diff -Naur sysklogd-1.5.orig/klogd.8 sysklogd-1.5.priv_sep/klogd.8 --- sysklogd-1.5.orig/klogd.8 2007-05-28 17:25:43.000000000 +0000 +++ sysklogd-1.5.priv_sep/klogd.8 2008-01-18 02:46:52.000000000 +0000 @@ -14,10 +14,19 @@ .RB [ " \-f " .I fname ] +.RB [ " \-u " +.I username +] +.RB [ " \-j " +.I chroot_dir +] .RB [ " \-iI " ] .RB [ " \-n " ] .RB [ " \-o " ] .RB [ " \-p " ] +.RB [ " \-P " +.I named_pipe +] .RB [ " \-s " ] .RB [ " \-k " .I fname @@ -41,6 +50,20 @@ .BI "\-f " file Log messages to the specified filename rather than to the syslog facility. .TP +.BI "\-u " username +Tells klogd to become the specified user and drop root privileges before +starting logging. +.TP +.BI "\-j " chroot_dir +Tells klogd to +.BR chroot (2) +into this directory after initializing. +This option is only valid if the \-u option is also used to run klogd +without root privileges. +Note that the use of this option will prevent \-i and \-I from working +unless you set up the chroot directory in such a way that klogd can still +read the kernel module symbols. +.TP .BI "\-i \-I" Signal the currently executing klogd daemon. Both of these switches control the loading/reloading of symbol information. The \-i switch signals the @@ -64,6 +87,13 @@ symbol information whenever an Oops string is detected in the kernel message stream. .TP +.B "-P" +Read kernel messages from an alternative location, instead of from +.IR /proc/kmsg . +If \- is the argument then messages will be read from \fBstdin\fP, otherwise +the argument must be the path of a FIFO named pipe created with +.BR mkfifo (1) . +.TP .B "\-s" Force \fBklogd\fP to use the system call interface to the kernel message buffers. diff -Naur sysklogd-1.5.orig/klogd.c sysklogd-1.5.priv_sep/klogd.c --- sysklogd-1.5.orig/klogd.c 2007-06-17 19:21:55.000000000 +0000 +++ sysklogd-1.5.priv_sep/klogd.c 2008-01-18 02:42:43.000000000 +0000 @@ -268,6 +268,8 @@ #include #include #include +#include +#include #include "klogd.h" #include "ksyms.h" #ifndef TESTING @@ -313,11 +315,16 @@ static FILE *output_file = (FILE *) 0; +static char *kmsg_file = NULL; /* NULL means default /proc/kmsg */ + static enum LOGSRC {none, proc, kernel} logsrc; int debugging = 0; int symbols_twice = 0; +char *server_user = NULL; +char *chroot_dir = NULL; +int log_flags = 0; /* Function prototypes. */ extern int ksyslog(int type, char *buf, int len); @@ -543,12 +550,29 @@ "console output."); } + /* Do we read kernel messages from a pipe? */ + if ( kmsg_file ) { + if ( !strcmp(kmsg_file, "-") ) + kmsg = fileno(stdin); + else { + if ( (kmsg = open(kmsg_file, O_RDONLY)) < 0 ) + { + fprintf(stderr, "klogd: Cannot open kmsg file, " \ + "%d - %s.\n", errno, strerror(errno)); + ksyslog(7, NULL, 0); + exit(1); + } + } + return proc; + } + /* * First do a stat to determine whether or not the proc based * file system is available to get kernel messages from. */ - if ( use_syscall || - ((stat(_PATH_KLOG, &sb) < 0) && (errno == ENOENT)) ) + if (!server_user && + (use_syscall || + ((stat(_PATH_KLOG, &sb) < 0) && (errno == ENOENT)))) { /* Initialize kernel logging. */ ksyslog(1, NULL, 0); @@ -972,6 +996,27 @@ } +static int drop_root(void) +{ + struct passwd *pw; + + if (!(pw = getpwnam(server_user))) return -1; + + if (!pw->pw_uid) return -1; + + if (chroot_dir) { + if (chdir(chroot_dir)) return -1; + if (chroot(".")) return -1; + } + + if (setgroups(0, NULL)) return -1; + if (setgid(pw->pw_gid)) return -1; + if (setuid(pw->pw_uid)) return -1; + + return 0; +} + + int main(argc, argv) int argc; @@ -990,7 +1035,7 @@ chdir ("/"); #endif /* Parse the command-line. */ - while ((ch = getopt(argc, argv, "c:df:iIk:nopsvx2")) != EOF) + while ((ch = getopt(argc, argv, "c:df:u:j:iIk:nopP:svx2")) != EOF) switch((char)ch) { case '2': /* Print lines with symbols twice. */ @@ -1012,6 +1057,10 @@ case 'I': SignalDaemon(SIGUSR2); return(0); + case 'j': /* chroot 'j'ail */ + chroot_dir = optarg; + log_flags |= LOG_NDELAY; + break; case 'k': /* Kernel symbol file. */ symfile = optarg; break; @@ -1024,9 +1073,15 @@ case 'p': SetParanoiaLevel(1); /* Load symbols on oops. */ break; + case 'P': /* Alternative kmsg file path */ + kmsg_file = strdup(optarg); + break; case 's': /* Use syscall interface. */ use_syscall = 1; break; + case 'u': /* Run as this user */ + server_user = optarg; + break; case 'v': printf("klogd %s.%s\n", VERSION, PATCHLEVEL); exit (1); @@ -1035,6 +1090,10 @@ break; } + if (chroot_dir && !server_user) { + fputs("'-j' is only valid with '-u'\n", stderr); + exit(1); + } /* Set console logging level. */ if ( log_level != (char *) 0 ) @@ -1144,7 +1203,7 @@ } } else - openlog("kernel", 0, LOG_KERN); + openlog("kernel", log_flags, LOG_KERN); /* Handle one-shot logging. */ @@ -1176,6 +1235,11 @@ kill (ppid, SIGTERM); #endif + if (server_user && drop_root()) { + syslog(LOG_ALERT, "klogd: failed to drop root"); + Terminate(); + } + /* The main loop. */ while (1) { diff -Naur sysklogd-1.5.orig/sysklogd.8 sysklogd-1.5.priv_sep/sysklogd.8 --- sysklogd-1.5.orig/sysklogd.8 2007-05-27 12:16:17.000000000 +0000 +++ sysklogd-1.5.priv_sep/sysklogd.8 2008-01-18 02:42:43.000000000 +0000 @@ -29,6 +29,12 @@ .RB [ " \-s " .I domainlist ] +.RB [ " \-u" +.IB username +] +.RB [ " \-j " +.I chroot_dir +] .RB [ " \-v " ] .SH DESCRIPTION .B Sysklogd @@ -150,6 +156,32 @@ no domain would be cut, you will have to specify two domains like: .BR "\-s north.de:infodrom.north.de" . .TP +.BI "\-u " "username" +This causes the +.B syslogd +daemon to become the named user before starting up logging. + +Note that when this option is in use, +.B syslogd +will open all log files as root when the daemon is first started; +however, after a +.B SIGHUP +the files will be reopened as the non-privileged user. You should +take this into account when deciding the ownership of the log files. +.TP +.BI "\-j " chroot_dir +Tells +.B syslogd +daemon to +.BR chroot (2) +into this directory after initializing. +This option is only valid if the \-u option is also used to run +.B syslogd +without root privileges. +Note that the use of this option will prevent +.B SIGHUP +from working which makes daemon reload practically impossible. +.TP .B "\-v" Print version and exit. .SH SIGNALS diff -Naur sysklogd-1.5.orig/syslogd.c sysklogd-1.5.priv_sep/syslogd.c --- sysklogd-1.5.orig/syslogd.c 2007-07-04 19:04:01.000000000 +0000 +++ sysklogd-1.5.priv_sep/syslogd.c 2008-01-18 02:51:30.000000000 +0000 @@ -538,6 +538,9 @@ #include #include +#include +#include + #include #include #include @@ -790,6 +793,9 @@ int NoHops = 1; /* Can we bounce syslog messages through an intermediate host. */ +char *server_user = NULL; /* user name to run server as */ +char *chroot_dir = NULL; /* user name to run server as */ + extern int errno; /* Function prototypes. */ @@ -830,6 +836,26 @@ static int create_inet_socket(); #endif +static int drop_root(void) +{ + struct passwd *pw; + + if (!(pw = getpwnam(server_user))) return -1; + + if (!pw->pw_uid) return -1; + + if (chroot_dir) { + if (chdir(chroot_dir)) return -1; + if (chroot(".")) return -1; + } + + if (initgroups(server_user, pw->pw_gid)) return -1; + if (setgid(pw->pw_gid)) return -1; + if (setuid(pw->pw_uid)) return -1; + + return 0; +} + int main(argc, argv) int argc; char **argv; @@ -886,7 +912,7 @@ funix[i] = -1; } - while ((ch = getopt(argc, argv, "a:dhf:l:m:np:rs:v")) != EOF) + while ((ch = getopt(argc, argv, "a:dhf:j:l:m:np:rs:u:v")) != EOF) switch((char)ch) { case 'a': if (nfunix < MAXFUNIX) @@ -903,9 +929,12 @@ case 'h': NoHops = 0; break; + case 'j': + chroot_dir = optarg; + break; case 'l': if (LocalHosts) { - fprintf (stderr, "Only one -l argument allowed," \ + fprintf(stderr, "Only one -l argument allowed, " "the first one is taken.\n"); break; } @@ -931,6 +960,9 @@ } StripDomains = crunch_list(optarg); break; + case 'u': + server_user = optarg; + break; case 'v': printf("syslogd %s.%s\n", VERSION, PATCHLEVEL); exit (0); @@ -941,6 +973,10 @@ if ((argc -= optind)) usage(); + if (chroot_dir && !server_user) { + fputs("'-j' is only valid with '-u'\n", stderr); + exit(1); + } #ifndef TESTING if ( !(Debug || NoFork) ) { @@ -1087,6 +1123,11 @@ kill (ppid, SIGTERM); #endif + if (server_user && drop_root()) { + dprintf("syslogd: failed to drop root\n"); + exit(1); + } + /* Main loop begins here. */ for (;;) { int nfds; @@ -1239,7 +1280,7 @@ int usage() { fprintf(stderr, "usage: syslogd [-drvh] [-l hostlist] [-m markinterval] [-n] [-p path]\n" \ - " [-s domainlist] [-f conffile]\n"); + " [-s domainlist] [-f conffile] [-j chrootjail] [-u username]\n"); exit(1); }