Race between cron and crontab

John Baldwin jhb at freebsd.org
Tue Jan 31 16:49:34 UTC 2012


A co-worker ran into a race between updating a cron tab via crontab(8) and 
cron(8) yesterday.  Specifically, cron(8) failed to notice that a crontab was 
updated.  The problem is that 1) by default our filesystems only use second 
granularity for timestamps and 2) cron only caches the seconds portion of a 
file's timestamp when checking for changes anyway.  This means that cron can 
miss updates to a spool directory if multiple updates to the directory are 
performed within a single second and cron wakes up to scan the spool directory 
within the same second and scans it before all of the updates are complete.

Specifically, when replacing a crontab, crontab(8) first creates a temporary 
file in /var/cron/tabs and then uses a rename to install it followed by 
touching the spool directory to update its modification time.  However, the 
creation of the temporary file already changes the modification time of the 
directory, and cron may "miss" the rename if it scans the directory in between 
the creation of the temporary file and the rename.

The "fix" I am planning to use locally is to simply force crontab(8) to sleep 
for a second before it touches the spool directory, thus ensuring that it the 
touch of the spool directory will use a later modification time than the 
creation of the temporary file.

Note that crontab -r is not affected by this race as it only does one atomic 
update to the directory (unlink()).

Index: crontab.c
===================================================================
--- crontab.c	(revision 225431)
+++ crontab.c	(working copy)
@@ -604,6 +604,15 @@ replace_cmd() {
 
 	log_it(RealUser, Pid, "REPLACE", User);
 
+	/*
+	 * Creating the 'tn' temp file has already updated the
+	 * modification time of the spool directory.  Sleep for a
+	 * second to ensure that poke_daemon() sets a later
+	 * modification time.  Otherwise, this can race with the cron
+	 * daemon scanning for updated crontabs.
+	 */
+	sleep(1);
+
 	poke_daemon();
 
 	return (0);


-- 
John Baldwin


More information about the freebsd-current mailing list