Package: cron
Version: 3.0pl1-212
Severity: normal
Tags: patch
When a hostname fills cron's fixed-size buffer, the mail-output path
can treat it as a NUL-terminated string even though gethostname() did
not supply a terminator. An AddressSanitizer harness reproduced a read
past the buffer. The relevant conditions are a maximum-length
administrator-controlled hostname, job output, and mail delivery. We
have not demonstrated a useful information leak or a production crash.
The attached Debian -212 patch reserves room for termination, checks
gethostname()'s return value, and uses a safe fallback on error. Its
regression test covers ordinary success, error, and a successful
full-buffer result without a NUL terminator.
The patched Debian source built in a disposable trixie container; the
focused regression and all 13 existing Debian integration tests
passed. Ubuntu versions 3.0pl1-200ubuntu1 and -209ubuntu1 have the
same issue. Debian and Ubuntu Security Teams recommended public bug
reports.
Regards,
Brett Sutton
Description: Terminate mail hostnames and handle lookup failures
Origin: other, private Daybreak audit
Forwarded: no
--- a/cron.h
+++ b/cron.h
@@ -345,3 +345,5 @@
extern char *DebugFlagNames[];
# endif /* DEBUGGING */
#endif /*MAIN_PROGRAM*/
+
+int cron_gethostname(char *, size_t);
--- a/do_command.c
+++ b/do_command.c
@@ -534,12 +534,12 @@
register char **env;
char **jobenv = build_env(e->envp);
auto char mailcmd[MAX_COMMAND];
- auto char hostname[MAXHOSTNAMELEN];
+ auto char hostname[MAXHOSTNAMELEN + 1];
char *content_type = env_get("CONTENT_TYPE",jobenv),
*content_transfer_encoding = env_get("CONTENT_TRANSFER_ENCODING",jobenv);
int encoding_auto = 0;
- (void) gethostname(hostname, MAXHOSTNAMELEN);
+ (void) cron_gethostname(hostname, sizeof hostname);
(void) snprintf(mailcmd, sizeof(mailcmd),
MAILARGS, MAILCMD, mailto);
if (!(mail = cron_popen(mailcmd, "w", e))) {
--- a/misc.c
+++ b/misc.c
@@ -792,3 +792,14 @@
return (offset);
}
#endif /* HAVE_TM_GMTOFF */
+
+int
+cron_gethostname(char *hostname, size_t size)
+{
+ if (size < sizeof "localhost")
+ return FALSE;
+ if (gethostname(hostname, size) != 0)
+ (void) strcpy(hostname, "localhost");
+ hostname[size - 1] = '\0';
+ return TRUE;
+}
\ No newline at end of file
--- /dev/null
+++ b/tests/hostname_regression.c
@@ -0,0 +1,49 @@
+#define MAIN_PROGRAM
+#define gethostname fake_gethostname
+#include "../misc.c"
+#undef gethostname
+#include <assert.h>
+
+static int hostname_mode;
+
+int
+fake_gethostname(char *name, size_t size)
+{
+ if (hostname_mode == 3) {
+ memset(name, 'h', size);
+ return 0;
+ }
+ if (hostname_mode == 1) {
+ memset(name, 'h', size);
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if (hostname_mode == 2) {
+ if (size <= MAXHOSTNAMELEN)
+ return -1;
+ memset(name, 'h', MAXHOSTNAMELEN);
+ name[MAXHOSTNAMELEN] = '\0';
+ return 0;
+ }
+ memcpy(name, "testhost", 9);
+ return 0;
+}
+
+
+int main(void) {
+ char hostname[MAXHOSTNAMELEN + 1];
+ hostname_mode = 1;
+ assert(cron_gethostname(hostname, sizeof hostname));
+ assert(strcmp(hostname, "localhost") == 0);
+ hostname_mode = 2;
+ assert(cron_gethostname(hostname, sizeof hostname));
+ assert(strlen(hostname) == MAXHOSTNAMELEN);
+ hostname_mode = 3;
+ assert(cron_gethostname(hostname, sizeof hostname));
+ assert(hostname[sizeof hostname - 1] == '\0');
+ assert(strlen(hostname) == MAXHOSTNAMELEN);
+ hostname_mode = 0;
+ assert(cron_gethostname(hostname, sizeof hostname));
+ assert(strcmp(hostname, "testhost") == 0);
+ return 0;
+}