Package: cron
Version: 3.0pl1-212
Severity: normal
Tags: patch
This is the already-public orphan-rescan crash tracked in Ubuntu as LP #1846025:
https://bugs.launchpad.net/ubuntu/+source/cron/+bug/1846025
When an orphaned crontab account becomes resolvable again,
check_orphans() invokes process_crontab() with old_db == NULL. If the
spool file then fails validation (for example, insecure mode or wrong
ownership), process_crontab() calls force_rescan_user(), which
dereferences old_db via find_user(). An isolated ASan/UBSan
reproduction crashed the daemon. The likely trigger is account/NSS
lifecycle plus an invalid protected spool file; ordinary users cannot
directly alter that spool, so attacker control is limited.
The attached one-line guard and focused regression test apply
independently to Debian cron 3.0pl1-212. The regression passes with
the fix and reproduces the crash when reverted. The patched Debian
source built in a disposable trixie container; all 13 existing
integration tests passed. The identical fix has been attached to the
existing Ubuntu report and acknowledged there.
Please consider the patch for Debian and any supported-release
backport. This is a follow-up to a known public issue, not a new
vulnerability claim.
Regards,
Brett Sutton
Description: Handle orphan rescans without an old database
Origin: other, private Daybreak audit
Forwarded: no
--- a/database.c
+++ b/database.c
@@ -554,7 +554,7 @@
user *u;
/* Remove from old DB and free resources */
- u = find_user(old_db, fname);
+ u = old_db != NULL ? find_user(old_db, fname) : NULL;
if (u != NULL) {
Debug(DLOAD, (" [delete old data]"))
unlink_user(old_db, u);
--- /dev/null
+++ b/tests/orphan_regression.c
@@ -0,0 +1,82 @@
+#define _GNU_SOURCE
+#define MAIN_PROGRAM
+#include "cron.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <assert.h>
+
+static int account_exists;
+static const char orphan_name[] = "orphan-audit";
+
+struct passwd *
+getpwnam(const char *name)
+{
+ static struct passwd pw;
+
+ if (!account_exists || strcmp(name, orphan_name) != 0)
+ return NULL;
+ pw.pw_name = (char *)orphan_name;
+ pw.pw_uid = getuid();
+ pw.pw_gid = getgid();
+ pw.pw_dir = "/tmp";
+ pw.pw_shell = "/bin/sh";
+ return &pw;
+}
+
+void
+log_it(char *username, int pid, char *event, char *detail)
+{
+ (void)username;
+ (void)pid;
+ (void)event;
+ (void)detail;
+}
+
+user *
+load_user(int fd, struct passwd *pw, char *uname, char *fname, char *tabname)
+{
+ (void)fd;
+ (void)pw;
+ (void)uname;
+ (void)fname;
+ (void)tabname;
+ return NULL;
+}
+
+void
+free_user(user *u)
+{
+ free(u->name);
+ free(u);
+}
+
+int
+main(void)
+{
+ char directory[] = "/tmp/cron-orphan-audit.XXXXXX";
+ cron_db db = {0};
+ int fd;
+
+ if (mkdtemp(directory) == NULL || chdir(directory) != 0 ||
+ mkdir(SPOOL_DIR, 0700) != 0)
+ return 2;
+ fd = open("crontabs/orphan-audit", O_CREAT | O_WRONLY, 0644);
+ if (fd < 0)
+ return 2;
+ {
+ const char contents[] = "* * * * * true\n";
+ write(fd, contents, strlen(contents));
+ }
+ close(fd);
+
+ /* First scan records a file whose account cannot be resolved. */
+ account_exists = 0;
+ load_database(&db);
+ /* A later NSS/account resolution triggers the old_db == NULL path. */
+ account_exists = 1;
+ check_orphans(&db);
+ assert(find_user(&db, orphan_name) != NULL);
+
+ return 0;
+}