[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Jessie update of cron?



On 2019-03-15 15:33, Mike Gabriel wrote:
> On  Fr 15 Mär 2019 15:11:11 CET, Christian Kastner wrote:
>> On 2019-03-15 14:52, Mike Gabriel wrote:
>>> Dear maintainer(s),
>>>
>>> The Debian LTS team would like to fix the security issues which are
>>> currently open in the Jessie version of cron:
>>> https://security-tracker.debian.org/tracker/CVE-2019-9704
>>> https://security-tracker.debian.org/tracker/CVE-2019-9705
>>> https://security-tracker.debian.org/tracker/CVE-2019-9706
>>> https://security-tracker.debian.org/tracker/CVE-2017-9525
>>>
>>> For Debian stretch, these issues have been marked as <no-dsa>. However,
>>> for Debian LTS, we would like to get those issues resolved.
>>>
>>> Would you like to take care of this yourself?
>>
>> I had planned to prepare fixes for jessie and stretch, but in
>> discussion with the security team, it was considered to wait
>> until these fixes migrate to buster before we fix stable and
>> oldstable.
>>
>> If you'd rather not wait, I can prepare fixes earlier, but not
>> before Sunday -- I should probably make Tuesday.
> 
> Ok. Please send a .debdiff for jessie to this list (and directly to me,
> too, so I don't miss it).

I was able to cherry-pick the relevant changes with only trivial
adaptations needed. The result built fine in a jessie chroot.

debdiff attached.

I've also pushed these proposed changes to a wip/jessie branch on Salsa.
I'll merge them into the official updates/jessie pending your ACK.

> I'll handle the upload and processing and also backport your changes to
> wheezy ELTS then.

Regards,
Christian
diff -u cron-3.0pl1/cron.h cron-3.0pl1/cron.h
--- cron-3.0pl1/cron.h
+++ cron-3.0pl1/cron.h
@@ -82,6 +82,7 @@
 #define	MAX_COMMAND	1000	/* max length of internally generated cmd */
 #define	MAX_TEMPSTR	1000	/* max length of envvar=value\0 strings */
 #define	MAX_ENVSTR	MAX_TEMPSTR	/* DO NOT change - buffer overruns otherwise */
+#define MAX_TAB_LINES	1000	/* max length of crontabs */
 #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	ROOT_USER	"root"	/* ditto */
@@ -101,6 +102,7 @@
 #define	CRON_TAB(u)	"%s/%s", SPOOL_DIR, u
 #define	REG		register
 #define	PPC_NULL	((char **)NULL)
+#define NHEADER_LINES   3
 
 #ifndef MAXHOSTNAMELEN
 #define MAXHOSTNAMELEN 64
@@ -126,6 +128,8 @@
 			;
 #endif /* DEBUGGING */
 
+#define Stringify_(x)	#x
+#define Stringify(x)	Stringify_(x)
 #define	MkLower(ch)	(isupper(ch) ? tolower(ch) : ch)
 #define	MkUpper(ch)	(islower(ch) ? toupper(ch) : ch)
 #define	Set_LineNum(ln)	{Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
diff -u cron-3.0pl1/crontab.1 cron-3.0pl1/crontab.1
--- cron-3.0pl1/crontab.1
+++ cron-3.0pl1/crontab.1
@@ -133,6 +133,14 @@
 /var/spool/cron/crontabs
 .fi
 .PP
+The files
+.I /etc/cron.allow
+and
+.I /etc/cron.deny
+if, they exist, must be either world-readable, or readable by group
+``crontab''. If they are not, then cron will deny access to all users until the
+permissions are fixed.
+.PP
 There is one file for each user's crontab under the /var/spool/cron/crontabs
 directory. Users are not allowed to edit the files under that directory
 directly to ensure that only users allowed by the system to run periodic tasks
diff -u cron-3.0pl1/crontab.c cron-3.0pl1/crontab.c
--- cron-3.0pl1/crontab.c
+++ cron-3.0pl1/crontab.c
@@ -46,8 +46,6 @@
 #endif
 
 
-#define NHEADER_LINES 3
-
 enum opt_t	{ opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
 
 #if DEBUGGING
@@ -886,7 +884,7 @@
 	 */
 	Set_LineNum(1 - NHEADER_LINES)
 	CheckErrorCount = 0;  eof = FALSE;
-	while (!CheckErrorCount && !eof) {
+	while (!CheckErrorCount && !eof && LineNumber < MAX_TAB_LINES + 2) {
 		switch (load_env(envstr, tmp)) {
 		case ERR:
 			eof = TRUE;
@@ -903,6 +901,13 @@
 		}
 	}
 
+	if (LineNumber >= MAX_TAB_LINES + 2) {
+		fprintf(stderr, "crontab is too long; maximum number of lines "
+				"is %d.\n", MAX_TAB_LINES);
+		fclose(tmp);  unlink(tn);
+		return (-1);
+	}
+
 	if (CheckErrorCount != 0) {
 		fprintf(stderr, "errors in crontab file, can't install.\n");
 		fclose(tmp);  unlink(tn);
diff -u cron-3.0pl1/database.c cron-3.0pl1/database.c
--- cron-3.0pl1/database.c
+++ cron-3.0pl1/database.c
@@ -66,7 +66,7 @@
         DIR		*dir;
 	struct stat	statbuf;
 	struct stat	syscron_stat;
-	DIR_T   	*dp;
+	DIR_T		*dp;
 	cron_db		new_db;
 	user		*u, *nu;
 #ifdef DEBIAN
@@ -595,10 +595,12 @@
 	/* Allocate an empty crontab with the specified mtime, add it to new DB */
         if ((u = (user *) malloc(sizeof(user))) == NULL) {
                 errno = ENOMEM;
+		return;
         }   
         if ((u->name = strdup(fname)) == NULL) {
                 free(u);
                 errno = ENOMEM;
+		return;
         }   
         u->mtime = old_mtime;
         u->crontab = NULL;
diff -u cron-3.0pl1/debian/NEWS cron-3.0pl1/debian/NEWS
--- cron-3.0pl1/debian/NEWS
+++ cron-3.0pl1/debian/NEWS
@@ -1,3 +1,13 @@
+cron (3.0pl1-127+deb8u2) unstable; urgency=medium
+
+  * As a reasonable protective measure, crontabs are now limited to 1000 lines
+    in length per crontab.
+    The maintainers find it very unlikely that longer crontabs exist; however,
+    if you do have a use case, please file a bug report with a brief rationale,
+    and we will consider raising this limit.
+
+ -- Christian Kastner <ckk@debian.org>  Sun, 10 Mar 2019 17:44:13 +0100
+
 cron (3.0pl1-119) unstable; urgency=low
 
     The semantics of the -L option of the cron daemon have changed: from
diff -u cron-3.0pl1/debian/changelog cron-3.0pl1/debian/changelog
--- cron-3.0pl1/debian/changelog
+++ cron-3.0pl1/debian/changelog
@@ -1,3 +1,29 @@
+cron (3.0pl1-127+deb8u2) jessie-security; urgency=medium
+
+  * SECURITY: Fix bypass of /etc/cron.{allow,deny} on failure to open
+    If these files exist, then they must be readable by the user executing
+    crontab(1). Users will now be denied by default if they aren't.
+    (LP: #1813833)
+  * SECURITY: Fix for possible DoS by use-after-free
+    A user reported a use-after-free condition in the cron daemon, leading to a
+    possible Denial-of-Service scenario by crashing the daemon.
+    (CVE-2019-9706) (Closes: #809167)
+  * SECURITY: DoS: Fix unchecked return of calloc()
+    Florian Weimer discovered that a missing check for the return value of
+    calloc() could crash the daemon, which could be triggered by a very
+    large crontab created by a user. (CVE-2019-9704)
+  * Enforce maximum crontab line count of 1000 to prevent a malicious user
+    from creating an excessivly large crontab. The daemon will log a warning
+    for existing files, and crontab(1) will refuse to create new ones.
+    (CVE-2019-9705)
+  * SECURITY: group crontab to root escalation
+    via postinst as described by Alexander Peslyak (Solar Designer) in
+    http://www.openwall.com/lists/oss-security/2017/06/08/3
+    (CVE-2017-9525)
+  * Add d/NEWS altering to the new 1000 lines limit.
+
+ -- Christian Kastner <ckk@debian.org>  Sun, 17 Mar 2019 14:12:24 +0100
+
 cron (3.0pl1-127+deb8u1) jessie; urgency=medium
 
   * d/cron.service: Use KillMode=process to kill only the daemon.
diff -u cron-3.0pl1/debian/postinst cron-3.0pl1/debian/postinst
--- cron-3.0pl1/debian/postinst
+++ cron-3.0pl1/debian/postinst
@@ -60,8 +60,32 @@
     # It has been disabled to suit cron alternative such as bcron. 
     cd $crondir/crontabs
     set +e
-    ls -1 | xargs -r -n 1 --replace=xxx  chown 'xxx:crontab' 'xxx'
-    ls -1 | xargs -r -n 1 chmod 600
+
+    # Iterate over each entry in the spool directory, perform some sanity
+    # checks (see CVE-2017-9525), and chown/chgroup the crontabs
+    for tab_name in *
+    do
+        tab_type=`stat -c '%F' "$tab_name"`
+        tab_links=`stat -c '%h' "$tab_name"`
+        tab_owner=`stat -c '%U' "$tab_name"`
+
+        if [ "$tab_type" != "regular file" -a "$tab_type" != "regular empty file" ]
+        then
+            echo "Warning: $tab_name is not a regular file!"
+            continue
+        elif [ "$tab_links" -ne 1 ]
+        then
+            echo "Warning: $tab_name has more than one hard link!"
+            continue
+        elif [ "$tab_name" != "$tab_owner" ]
+        then
+            echo "Warning: $tab_name name differs from owner $tab_owner!"
+            continue
+        fi
+
+		chown "$tab_owner:crontab" "$tab_name"
+		chmod 600 "$tab_name"
+    done
     set -e
 fi
 
diff -u cron-3.0pl1/entry.c cron-3.0pl1/entry.c
--- cron-3.0pl1/entry.c
+++ cron-3.0pl1/entry.c
@@ -108,6 +108,10 @@
 	 */
 
 	e = (entry *) calloc(sizeof(entry), sizeof(char));
+	if (e == NULL) {
+		log_it("CRON", getpid(), "OOM", "Out of memory parsing crontab");
+		return NULL;
+	}
 
 	if (ch == '@') {
 		/* all of these should be flagged and load-limited; i.e.,
diff -u cron-3.0pl1/misc.c cron-3.0pl1/misc.c
--- cron-3.0pl1/misc.c
+++ cron-3.0pl1/misc.c
@@ -479,7 +479,23 @@
 		init = TRUE;
 #if defined(ALLOW_FILE) && defined(DENY_FILE)
 		allow = fopen(ALLOW_FILE, "r");
+		if (allow == NULL) {
+			/* Only if the file does not exist do we ignore the
+			 * error. Otherwise, we deny by default.
+			 */
+			if (errno != ENOENT) {
+				perror(ALLOW_FILE);
+				return FALSE;
+			}
+		}
 		deny = fopen(DENY_FILE, "r");
+		if (allow == NULL) {
+			/* See above */
+			if (errno != ENOENT) {
+				perror(DENY_FILE);
+				return FALSE;
+			}
+		}
 		Debug(DMISC, ("allow/deny enabled, %d/%d\n", !!allow, !!deny))
 #else
 		allow = NULL;
diff -u cron-3.0pl1/user.c cron-3.0pl1/user.c
--- cron-3.0pl1/user.c
+++ cron-3.0pl1/user.c
@@ -240,6 +240,7 @@
 	/*
 	 * load the crontab
 	 */
+	Set_LineNum(1)
 	do {
 		status = load_env(envstr, file);
 		switch (status) {
@@ -289,7 +290,19 @@
 			}
 			break;
 		}
-	} while (status >= OK);
+	/* When counting lines, ignore the user-hidden header part, and account
+	 * for idiosyncrasies of LineNumber manipulation
+	 */
+	} while (status >= OK && LineNumber < MAX_TAB_LINES + NHEADER_LINES + 2);
+
+	if (LineNumber >= MAX_TAB_LINES + NHEADER_LINES + 2) {
+		log_it(fname, getpid(), "ERROR", "crontab must not be longer "
+				"than " Stringify(MAX_TAB_LINES) " lines, "
+				"this crontab file will be ignored");
+		free_user(u);
+		u = NULL;
+		goto done;
+	}
 
  done:
 	env_free(envp);

Reply to: