Package: cron
Version: 3.0pl1-197
Severity: minor
Tags: patch

Dear Maintainer,

`crontab <file>` silently truncates the file path argument at 100 chars
instead of rejecting a too-long path, then fails against the truncated
path with a misleading "No such file or directory" or "Not a regular
file" error that doesn't match the path the user actually passed.

Root cause: cron.h defines

    #define MAX_FNAME 100 /* max length of internally generated fn */

That buffer is meant for cron's own internally-generated filenames, but
crontab.c reuses the same constant for the buffer that holds the
user-supplied CLI path argument:

    static char Filename[MAX_FNAME];   /* crontab.c:60 */
    ...
    (void) strncpy (Filename, argv[optind], (sizeof Filename)-1);
    Filename[(sizeof Filename)-1] = '\0';   /* crontab.c:255-256 */

There is no length check before the strncpy, so any path >=100 chars
(99 usable chars + NUL) is silently truncated. The program then tries
to open/stat the truncated path, which either doesn't exist (ENOENT,
"No such file or directory") or happens to resolve to an existing
directory ("Not a regular file") -- neither error message reflects
what actually went wrong, which makes this very confusing to debug:
the file genuinely exists at the path the user gave, but crontab is
silently operating on a different, truncated path.

Reproduction (byte-precision, confirmed on 3.0pl1-197):

    $ f=/tmp/$(python3 -c "print('a'*86)")/x   # 99 chars total
    $ mkdir -p "$(dirname "$f")"; echo '* * * * * true' > "$f"
    $ crontab "$f"; echo $?
    0

    $ f=/tmp/$(python3 -c "print('a'*87)")/x   # 100 chars total
    $ mkdir -p "$(dirname "$f")"; echo '* * * * * true' > "$f"
    $ crontab "$f"
    /tmp/aaaa....aaa: Not a regular file.
    (the printed path is the truncated 99-char string, cut off before
    the trailing "/x" -- it happens to equal the parent directory,
    which is why stat() succeeds but S_ISREG() fails)

Proposed fix: reject with a clear error instead of truncating silently.
Minimal patch against crontab.c, just before the existing strncpy at
line 255:

--- a/crontab.c
+++ b/crontab.c
@@ -252,6 +252,11 @@
                if (argv[optind] != NULL) {
                        Option = opt_replace;
+                       if (strlen(argv[optind]) >= sizeof(Filename)) {
+                               fprintf(stderr,
+                                       "%s: pathname too long (max %zu 
chars)\n",
+                                       argv[optind], sizeof(Filename) - 1);
+                               exit(ERROR_EXIT);
+                       }
                        (void) strncpy (Filename, argv[optind], (sizeof 
Filename)-1);
                        Filename[(sizeof Filename)-1] = '\0';

This preserves the existing MAX_FNAME=100 limit used elsewhere in the
codebase for internally-generated filenames, and just stops it from
silently truncating a user-supplied path into a different, unintended
path.

-- System Information:
Debian Release: trixie
Architecture: amd64
Kernel: Linux 6.12.101+deb13-amd64

Reply via email to