Skip site navigation (1)Skip section navigation (2)
Date:      Fri,  2 Aug 2002 23:33:15 +0200 (CEST)
From:      Pawel Jakub Dawidek <nick@garage.freebsd.pl>
To:        FreeBSD-gnats-submit@FreeBSD.org
Subject:   bin/41271: Non-suid-crontab.
Message-ID:  <20020802213315.CA1943ABD6C@milla.ask33.net>

next in thread | raw e-mail | index | archive | help

>Number:         41271
>Category:       bin
>Synopsis:       Non-suid-crontab.
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Fri Aug 02 14:40:01 PDT 2002
>Closed-Date:
>Last-Modified:
>Originator:     Pawel Jakub Dawidek
>Release:        FreeBSD 4.6-STABLE i386
>Organization:
>Environment:
System: FreeBSD milla.ask33.net 4.6-STABLE FreeBSD 4.6-STABLE #9: Wed Jul 31 21:18:46 CEST 2002 root@milla.ask33.net:/usr/obj/usr/src/sys/MILLA i386


>Description:
	Now crontab(1) is a set-uid-root binary. This is not needed, crontab
	could be only set-gid on unprivileged group, for example ,,crontab''.
	I'm sending a patch that is a port of crontab from OpenBSD, but with
	serious hole fixed. This patch fix some little bugs in FreeBSD
	crontab version too. I've added also path size checks.
>How-To-Repeat:
>Fix:

diff -ru /usr/src/usr.sbin/cron/cron/compat.h cron/cron/compat.h
--- /usr/src/usr.sbin/cron/cron/compat.h	Sat Aug 28 03:15:49 1999
+++ cron/cron/compat.h	Thu Aug  1 20:32:19 2002
@@ -109,7 +109,7 @@
 #ifdef POSIX
 #include <unistd.h>
 #ifdef _POSIX_SAVED_IDS
-# define HAVE_SAVED_UIDS
+# define HAVE_SAVED_GIDS
 #endif
 #endif
 
@@ -133,8 +133,4 @@
 
 #if !defined(UNICOS) && !defined(UNIXPC)
 # define HAS_FCHOWN
-#endif
-
-#if !defined(UNICOS) && !defined(UNIXPC)
-# define HAS_FCHMOD
 #endif
diff -ru /usr/src/usr.sbin/cron/cron/cron.h cron/cron/cron.h
--- /usr/src/usr.sbin/cron/cron/cron.h	Tue May 29 01:37:26 2001
+++ cron/cron/cron.h	Fri Aug  2 03:12:50 2002
@@ -74,6 +74,8 @@
 #define	MAX_TEMPSTR	100	/* obvious */
 #define	MAX_UNAME	20	/* max length of username, should be overkill */
 #define	ROOT_UID	0	/* don't change this, it really must be root */
+#define	WHEEL_GID	0	/* don't change this, it really must be wheel */
+#define CRONTAB_GID	88	/* gid of "crontab" group */
 #define	ROOT_USER	"root"	/* ditto */
 
 				/* NOTE: these correspond to DebugFlagNames,
@@ -219,7 +221,7 @@
 		set_debug_flags __P((char *)),
 		get_char __P((FILE *)),
 		get_string __P((char *, int, FILE *, char *)),
-		swap_uids __P((void)),
+		swap_gids __P((void)),
 		load_env __P((char *, FILE *)),
 		cron_pclose __P((FILE *)),
 		strcmp_until __P((char *, char *, int)),
diff -ru /usr/src/usr.sbin/cron/cron/database.c cron/cron/database.c
--- /usr/src/usr.sbin/cron/cron/database.c	Sat Aug 28 03:15:50 1999
+++ cron/cron/database.c	Fri Aug  2 03:38:49 2002
@@ -87,7 +87,7 @@
 	new_db.head = new_db.tail = NULL;
 
 	if (syscron_stat.st_mtime) {
-		process_crontab("root", "*system*",
+		process_crontab(ROOT_USER, "*system*",
 				SYSCRONTAB, &syscron_stat,
 				&new_db, old_db);
 	}
@@ -113,9 +113,13 @@
 		if (dp->d_name[0] == '.')
 			continue;
 
-		(void) strncpy(fname, dp->d_name, sizeof(fname));
-		fname[sizeof(fname)-1] = '\0';
+		(void) strncpy(fname, dp->d_name, sizeof fname - 1);
+		fname[sizeof fname - 1] = '\0';
+		if (strlen(fname) >= sizeof fname - 1)
+			continue;	/* XXX log? */
 		(void) snprintf(tabname, sizeof tabname, CRON_TAB(fname));
+		if (strlen(tabname) >= sizeof tabname - 1)
+			continue;	/* XXX log? */
 
 		process_crontab(fname, fname, tabname,
 				&statbuf, &new_db, old_db);
@@ -182,7 +186,6 @@
 	cron_db	*db;
 	char	*name;
 {
-	char	*env_get();
 	user	*u;
 
 	for (u = db->head;  u != NULL;  u = u->next)
@@ -204,6 +207,8 @@
 	struct passwd	*pw = NULL;
 	int		crontab_fd = OK - 1;
 	user		*u;
+	uid_t		pwuid;
+	gid_t		pwgid;
 
 	if (strcmp(fname, "*system*") && !(pw = getpwnam(uname))) {
 		/* file doesn't have a user in passwd file.
@@ -211,6 +216,14 @@
 		log_it(fname, getpid(), "ORPHAN", "no passwd entry");
 		goto next_crontab;
 	}
+	/* this have to be done, because of fake "*system*" files */
+	if (pw) {
+		pwuid = pw->pw_uid;
+		pwgid = pw->pw_gid;
+	} else {
+		pwuid = ROOT_UID;
+		pwgid = WHEEL_GID;
+	}
 
 	if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK) {
 		/* crontab not accessible?
@@ -221,6 +234,26 @@
 
 	if (fstat(crontab_fd, statbuf) < OK) {
 		log_it(fname, getpid(), "FSTAT FAILED", tabname);
+		goto next_crontab;
+	}
+	if (!S_ISREG(statbuf->st_mode)) {
+		log_it(fname, getpid(), "NOT REGULAR", tabname);
+		goto next_crontab;
+	}
+	if ((statbuf->st_mode & 07777) != 0600) {
+		log_it(fname, getpid(), "BAD FILE MODE", tabname);
+		goto next_crontab;
+	}
+	if (statbuf->st_uid != pwuid) {
+		log_it(fname, getpid(), "WRONG FILE USER OWNER", tabname);
+		goto next_crontab;
+	}
+	if (statbuf->st_gid != pwgid) {
+		log_it(fname, getpid(), "WRONG FILE GROUP OWNER", tabname);
+		goto next_crontab;
+	}
+	if (statbuf->st_nlink != 1) {
+		log_it(fname, getpid(), "BAD LINK COUNT", tabname);
 		goto next_crontab;
 	}
 
diff -ru /usr/src/usr.sbin/cron/crontab/Makefile cron/crontab/Makefile
--- /usr/src/usr.sbin/cron/crontab/Makefile	Wed Apr 25 14:09:24 2001
+++ cron/crontab/Makefile	Fri Aug  2 03:47:20 2002
@@ -8,7 +8,8 @@
 
 BINDIR=	/usr/bin
 BINOWN=	root
-BINMODE=4555
+BINGRP=	crontab
+BINMODE=2555
 INSTALLFLAGS=-fschg
 
 .include <bsd.prog.mk>
diff -ru /usr/src/usr.sbin/cron/crontab/crontab.c cron/crontab/crontab.c
--- /usr/src/usr.sbin/cron/crontab/crontab.c	Sat Jun 16 05:18:37 2001
+++ cron/crontab/crontab.c	Fri Aug  2 09:56:36 2002
@@ -57,7 +57,7 @@
 
 static	PID_T		Pid;
 static	char		User[MAX_UNAME], RealUser[MAX_UNAME];
-static	char		Filename[MAX_FNAME];
+static	char		Filename[MAX_FNAME + 1], TempFilename[MAX_FNAME + 1];
 static	FILE		*NewCrontab;
 static	int		CheckErrorCount;
 static	enum opt_t	Option;
@@ -69,6 +69,7 @@
 			check_error __P((char *)),
 			parse_args __P((int c, char *v[]));
 static	int		replace_cmd __P((void));
+static	void		clean_turds(int);
 
 
 static void
@@ -101,7 +102,6 @@
 	setlinebuf(stderr);
 #endif
 	parse_args(argc, argv);		/* sets many globals, opens a file */
-	set_cron_uid();
 	set_cron_cwd();
 	if (!allowed(User)) {
 		warnx("you (%s) are not allowed to use this program", User);
@@ -200,20 +200,12 @@
 		if (!strcmp(Filename, "-")) {
 			NewCrontab = stdin;
 		} else {
-			/* relinquish the setuid status of the binary during
-			 * the open, lest nonroot users read files they should
-			 * not be able to read.  we can't use access() here
-			 * since there's a race condition.  thanks go out to
-			 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
-			 * the race.
-			 */
-
-			if (swap_uids() < OK)
-				err(ERROR_EXIT, "swapping uids");
+			if (swap_gids() < OK)
+				err(ERROR_EXIT, "swapping gids");
 			if (!(NewCrontab = fopen(Filename, "r")))
 				err(ERROR_EXIT, "%s", Filename);
-			if (swap_uids() < OK)
-				err(ERROR_EXIT, "swapping uids back");
+			if (swap_gids() < OK)
+				err(ERROR_EXIT, "swapping gids back");
 		}
 	}
 
@@ -224,12 +216,14 @@
 
 static void
 list_cmd() {
-	char	n[MAX_FNAME];
+	char	n[MAX_FNAME + 1];
 	FILE	*f;
 	int	ch, x;
 
 	log_it(RealUser, Pid, "LIST", User);
-	(void) sprintf(n, CRON_TAB(User));
+	(void) snprintf(n, sizeof n, CRON_TAB(User));
+	if (strlen(n) >= sizeof n - 1)
+		err(ERROR_EXIT, "path too long");
 	if (!(f = fopen(n, "r"))) {
 		if (errno == ENOENT)
 			errx(ERROR_EXIT, "no crontab for %s", User);
@@ -266,7 +260,7 @@
 
 static void
 delete_cmd() {
-	char	n[MAX_FNAME];
+	char	n[MAX_FNAME + 1];
 	int ch, first;
 
 	if (isatty(STDIN_FILENO)) {
@@ -279,7 +273,9 @@
 	}
 
 	log_it(RealUser, Pid, "DELETE", User);
-	(void) sprintf(n, CRON_TAB(User));
+	(void) snprintf(n, sizeof n, CRON_TAB(User));
+	if (strlen(n) >= sizeof n - 1)
+		err(ERROR_EXIT, "path too long");
 	if (unlink(n)) {
 		if (errno == ENOENT)
 			errx(ERROR_EXIT, "no crontab for %s", User);
@@ -301,17 +297,18 @@
 
 static void
 edit_cmd() {
-	char		n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
+	char		n[MAX_FNAME + 1], q[MAX_TEMPSTR], *editor;
 	FILE		*f;
 	int		ch, t, x;
 	struct stat	statbuf, fsbuf;
 	time_t		mtime;
 	WAIT_T		waiter;
 	PID_T		pid, xpid;
-	mode_t		um;
 
 	log_it(RealUser, Pid, "BEGIN EDIT", User);
-	(void) sprintf(n, CRON_TAB(User));
+	(void) snprintf(n, sizeof n, CRON_TAB(User));
+	if (strlen(n) >= sizeof n - 1)
+		err(ERROR_EXIT, "path too long");
 	if (!(f = fopen(n, "r"))) {
 		if (errno != ENOENT)
 			err(ERROR_EXIT, "%s", n);
@@ -320,20 +317,19 @@
 			err(ERROR_EXIT, _PATH_DEVNULL);
 	}
 
-	um = umask(077);
-	(void) sprintf(Filename, "/tmp/crontab.XXXXXXXXXX");
-	if ((t = mkstemp(Filename)) == -1) {
-		warn("%s", Filename);
-		(void) umask(um);
-		goto fatal;
-	}
-	(void) umask(um);
+	strncpy(Filename, "/tmp/crontab.XXXXXXXXXX", sizeof Filename - 1);
+	Filename[sizeof Filename - 1] = '\0';
+	if (strlen(Filename) >= sizeof Filename - 1)
+		err(ERROR_EXIT, "path too long");
+	if ((t = mkstemp(Filename)) == -1)
+		err(ERROR_EXIT, "%s", Filename);
 #ifdef HAS_FCHOWN
 	if (fchown(t, getuid(), getgid()) < 0) {
+		warn("fchown");
 #else
 	if (chown(Filename, getuid(), getgid()) < 0) {
+		warn("chown");
 #endif
-		warn("fchown");
 		goto fatal;
 	}
 	if (!(NewCrontab = fdopen(t, "r+"))) {
@@ -402,8 +398,8 @@
 		goto fatal;
 	case 0:
 		/* child */
-		if (setuid(getuid()) < 0)
-			err(ERROR_EXIT, "setuid(getuid())");
+		if (setgid(getgid()) < 0)
+			err(ERROR_EXIT, "setgid(getgid())");
 		if (chdir("/tmp") < 0)
 			err(ERROR_EXIT, "chdir(/tmp)");
 		if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR)
@@ -491,9 +487,10 @@
  */
 static int
 replace_cmd() {
-	char	n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
+	char	n[MAX_FNAME + 1], envstr[MAX_ENVSTR];
 	FILE	*tmp;
-	int	ch, eof;
+	int	ch, eof, fd;
+	int	error = 0;
 	entry	*e;
 	time_t	now = time(NULL);
 	char	**envp = env_init();
@@ -503,13 +500,28 @@
 		return (-2);
 	}
 
-	(void) sprintf(n, "tmp.%d", Pid);
-	(void) sprintf(tn, CRON_TAB(n));
-	if (!(tmp = fopen(tn, "w+"))) {
-		warn("%s", tn);
+	(void) snprintf(TempFilename, sizeof TempFilename,
+	    CRON_TAB("tmp.XXXXXXXXX"));
+	if (strlen(TempFilename) >= sizeof TempFilename - 1) {
+		TempFilename[0] = '\0';
+		warn("path too long");
+		return (-2);
+	}
+
+	if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) {
+		warn("%s", TempFilename);
+		if (fd != -1) {
+			close(fd);
+			unlink(TempFilename);
+		}
+		TempFilename[0] = '\0';
 		return (-2);
 	}
 
+	(void) signal(SIGHUP, clean_turds);
+	(void) signal(SIGINT, clean_turds);
+	(void) signal(SIGQUIT, clean_turds);
+
 	/* write a signature at the top of the file.
 	 *
 	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
@@ -528,9 +540,10 @@
 	fflush(tmp);  rewind(tmp);
 
 	if (ferror(tmp)) {
-		warnx("error while writing new crontab to %s", tn);
-		fclose(tmp);  unlink(tn);
-		return (-2);
+		warnx("error while writing new crontab to %s", TempFilename);
+		fclose(tmp);
+		error = -2;
+		goto done;
 	}
 
 	/* check the syntax of the file being installed.
@@ -559,49 +572,54 @@
 
 	if (CheckErrorCount != 0) {
 		warnx("errors in crontab file, can't install");
-		fclose(tmp);  unlink(tn);
-		return (-1);
+		fclose(tmp);
+		error = -1;
+		goto done;
 	}
 
 #ifdef HAS_FCHOWN
-	if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
+	if (fchown(fileno(tmp), getuid(), getgid()) < OK) {
+		warn("fchown");
 #else
-	if (chown(tn, ROOT_UID, -1) < OK)
-#endif
-	{
+	if (chown(TempFilename, getuid(), getgid()) < OK) {
 		warn("chown");
-		fclose(tmp);  unlink(tn);
-		return (-2);
-	}
-
-#ifdef HAS_FCHMOD
-	if (fchmod(fileno(tmp), 0600) < OK)
-#else
-	if (chmod(tn, 0600) < OK)
 #endif
-	{
-		warn("chown");
-		fclose(tmp);  unlink(tn);
-		return (-2);
+		fclose(tmp); 
+		error = -2;
+		goto done;
 	}
 
 	if (fclose(tmp) == EOF) {
 		warn("fclose");
-		unlink(tn);
-		return (-2);
+		error = -2;
+		goto done;
 	}
 
-	(void) sprintf(n, CRON_TAB(User));
-	if (rename(tn, n)) {
-		warn("error renaming %s to %s", tn, n);
-		unlink(tn);
-		return (-2);
+	(void) snprintf(n, sizeof n, CRON_TAB(User));
+	if (strlen(n) >= sizeof n - 1) {
+		warn("path too long");
+		error = -2;
+		goto done;
 	}
+	if (rename(TempFilename, n)) {
+		warn("error renaming %s to %s", TempFilename, n);
+		error = -2;
+		goto done;
+	}
+	TempFilename[0] = '\0';
 	log_it(RealUser, Pid, "REPLACE", User);
 
 	poke_daemon();
 
-	return (0);
+done:
+	(void) signal(SIGHUP, SIG_DFL);
+	(void) signal(SIGINT, SIG_DFL);
+	(void) signal(SIGQUIT, SIG_DFL);
+	if (TempFilename[0]) {
+		(void) unlink(TempFilename);
+		TempFilename[0] = '\0';
+	}
+	return (error);
 }
 
 
@@ -623,4 +641,20 @@
 		return;
 	}
 #endif /*USE_UTIMES*/
+}
+
+
+static void
+clean_turds(signo)
+	int signo;
+{
+	int save_errno = errno;
+
+	if (TempFilename[0])
+		(void) unlink(TempFilename);
+	if (signo) {
+		(void) signal(signo, SIG_DFL);
+		(void) raise(signo);
+	}
+	errno = save_errno;
 }
diff -ru /usr/src/usr.sbin/cron/lib/misc.c cron/lib/misc.c
--- /usr/src/usr.sbin/cron/lib/misc.c	Mon Apr 29 00:45:53 2002
+++ cron/lib/misc.c	Fri Aug  2 03:32:07 2002
@@ -210,7 +210,11 @@
 	 */
 	if (stat(SPOOL_DIR, &sb) < OK && errno == ENOENT) {
 		warn("%s", SPOOL_DIR);
-		if (OK == mkdir(SPOOL_DIR, 0700)) {
+		if (OK == mkdir(SPOOL_DIR, 0730)) {
+			if (OK != chown(SPOOL_DIR, ROOT_UID, CRONTAB_GID)) {
+				rmdir(SPOOL_DIR);
+				err(ERROR_EXIT, "%s: chown", SPOOL_DIR);
+			}
 			warnx("%s: created", SPOOL_DIR);
 			stat(SPOOL_DIR, &sb);
 		} else {
@@ -219,6 +223,20 @@
 	}
 	if (!(sb.st_mode & S_IFDIR))
 		err(ERROR_EXIT, "'%s' is not a directory, bailing out", SPOOL_DIR);
+	if (sb.st_uid != ROOT_UID || sb.st_gid != CRONTAB_GID) {
+		if (OK != chown(SPOOL_DIR, ROOT_UID, CRONTAB_GID)) {
+			rmdir(SPOOL_DIR);
+			err(ERROR_EXIT, "%s: chown", SPOOL_DIR);
+		}
+		warnx("%s: set owner user and group", SPOOL_DIR);
+	}
+	if ((sb.st_mode & 07777) != 0730) {
+		if (OK != chmod(SPOOL_DIR, 0730)) {
+			rmdir(SPOOL_DIR);
+			err(ERROR_EXIT, "%s: chmod", SPOOL_DIR);
+		}
+		warnx("%s: set permissions", SPOOL_DIR);
+        }
 }
 
 
@@ -244,15 +262,19 @@
 	}
 
 	if (!fp) {
-		char	pidfile[MAX_FNAME];
+		char	pidfile[MAX_FNAME + 1];
 		char	buf[MAX_TEMPSTR];
 		int	fd, otherpid;
 
-		(void) sprintf(pidfile, PIDFILE, PIDDIR);
+		(void) snprintf(pidfile, sizeof pidfile, PIDFILE, PIDDIR);
+		if (strlen(pidfile) >= sizeof pidfile - 1) {
+			log_it("CRON", getpid(), "DEATH", "path too long");
+			errx(ERROR_EXIT, "path too long");
+		}
 		if ((-1 == (fd = open(pidfile, O_RDWR|O_CREAT, 0644)))
 		    || (NULL == (fp = fdopen(fd, "r+")))
 		    ) {
-			sprintf(buf, "can't open or create %s: %s",
+			snprintf(buf, sizeof buf, "can't open or create %s: %s",
 				pidfile, strerror(errno));
 			log_it("CRON", getpid(), "DEATH", buf);
 			errx(ERROR_EXIT, "%s", buf);
@@ -262,7 +284,8 @@
 			int save_errno = errno;
 
 			fscanf(fp, "%d", &otherpid);
-			sprintf(buf, "can't lock %s, otherpid may be %d: %s",
+			snprintf(buf, sizeof buf,
+				"can't lock %s, otherpid may be %d: %s",
 				pidfile, otherpid, strerror(save_errno));
 			log_it("CRON", getpid(), "DEATH", buf);
 			errx(ERROR_EXIT, "%s", buf);
@@ -390,7 +413,7 @@
 	char line[MAX_TEMPSTR];
 
 	rewind(file);
-	while (fgets(line, MAX_TEMPSTR, file)) {
+	while (fgets(line, sizeof line, file)) {
 		if (line[0] != '\0')
 			if (line[strlen(line)-1] == '\n')
 				line[strlen(line)-1] = '\0';
@@ -410,20 +433,13 @@
 allowed(username)
 	char *username;
 {
-	static int	init = FALSE;
-	static FILE	*allow, *deny;
+	static FILE	*allow = NULL, *deny = NULL;
 
-	if (!init) {
-		init = TRUE;
 #if defined(ALLOW_FILE) && defined(DENY_FILE)
-		allow = fopen(ALLOW_FILE, "r");
-		deny = fopen(DENY_FILE, "r");
-		Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
-#else
-		allow = NULL;
-		deny = NULL;
+	allow = fopen(ALLOW_FILE, "r");
+	deny = fopen(DENY_FILE, "r");
+	Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
 #endif
-	}
 
 	if (allow)
 		return (in_file(username, allow));
@@ -591,7 +607,7 @@
 			*dst++ = '^';
 			*dst++ = '?';
 		} else {			/* parity character */
-			sprintf(dst, "\\%03o", ch);
+			snprintf(dst, 5, "\\%03o", ch);
 			dst += 4;
 		}
 	}
@@ -645,11 +661,9 @@
 #endif /*MAIL_DATE*/
 
 
-#ifdef HAVE_SAVED_UIDS
-static int save_euid;
-int swap_uids() { save_euid = geteuid(); return seteuid(getuid()); }
-int swap_uids_back() { return seteuid(save_euid); }
-#else /*HAVE_SAVED_UIDS*/
-int swap_uids() { return setreuid(geteuid(), getuid()); }
-int swap_uids_back() { return swap_uids(); }
-#endif /*HAVE_SAVED_UIDS*/
+#ifdef HAVE_SAVED_GIDS
+static int save_egid;
+int swap_gids() { save_egid = getegid(); return setegid(getgid()); }
+#else /*HAVE_SAVED_GIDS*/
+int swap_gids() { return setregid(getegid(), getgid()); }
+#endif /*HAVE_SAVED_GIDS*/
>Release-Note:
>Audit-Trail:
>Unformatted:

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20020802213315.CA1943ABD6C>