oss-sec mailing list archives

CVE Request: OpenSMTPD <= 5.7.2 buffer overflow


From: "Jason A. Donenfeld" <Jason () zx2c4 com>
Date: Mon, 5 Oct 2015 17:00:52 +0200

Hello,

As reported in [1], OpenSMTPD <= 5.7.2 has a remotely exploitable
buffer overflow. This overflow requires a CVE. A PoC is attached to
[1].

OpenSMTPD 5.7.3 was released with fixes, and the release notes follow
below. There may be other vulnerabilities also fixed by this release.
A full diff follows for analysis and additional CVE assignment, in
case that is necessary.

Thanks,
Jason

[1] http://seclists.org/oss-sec/2015/q4/25


---------- Forwarded message ----------
From: Gilles Chehade <gilles () poolp org>
Date: Mon, Oct 5, 2015 at 3:30 PM
Subject: Announce: OpenSMTPD 5.7.3 released
To: misc () opensmtpd org

[snipped]

Issues fixed in this release (since 5.7.2):
===========================================

- fix an mda buffer truncation bug which allows a user to create forward
  files that pass session checks but fail delivery later down the chain,
  within the user mda [0]
- fix remote buffer overflow in unprivileged pony process [1]
- reworked offline enqueue to better protect against hardlink attacks [2]


[0] reported by Holger Jahn
[1] reported by Jason A. Donenfeld
[2] reported by Qualys Security

------ DIFF -----

diff -ru opensmtpd-5.7.2p1/smtpd/enqueue.c opensmtpd-5.7.3p1/smtpd/enqueue.c
--- opensmtpd-5.7.2p1/smtpd/enqueue.c        2015-10-02 01:46:21.000000000 +0200
+++ opensmtpd-5.7.3p1/smtpd/enqueue.c        2015-10-05 13:33:41.000000000 +0200
@@ -795,12 +795,25 @@
         FILE        *fp;
         int         i, fd, ch;
         mode_t         omode;
+        uint64_t        rnd;
+        int         ret;

         if (ckdir(PATH_SPOOL PATH_OFFLINE, 01777, 0, 0, 0) == 0)
                 errx(EX_UNAVAILABLE, "error in offline directory setup");

-        if (! bsnprintf(path, sizeof(path), "%s%s/%lld.XXXXXXXXXX", PATH_SPOOL,
-                PATH_OFFLINE, (long long int) time(NULL)))
+        do {
+                rnd = generate_uid();
+                if (! bsnprintf(path, sizeof(path),
"%s%s/%016"PRIx64, PATH_SPOOL,
+                        PATH_OFFLINE, rnd))
+                        err(EX_UNAVAILABLE, "snprintf");
+                ret = mkdir(path, 0700);
+                if (ret == -1)
+                        if (errno != EEXIST)
+                                err(EX_UNAVAILABLE, "mkdir");
+        } while (ret == -1);
+
+        if (! bsnprintf(path, sizeof(path),
"%s%s/%016"PRIx64"/%lld.XXXXXXXXXX", PATH_SPOOL,
+                PATH_OFFLINE, rnd, (long long int) time(NULL)))
                 err(EX_UNAVAILABLE, "snprintf");

         omode = umask(7077);
diff -ru opensmtpd-5.7.2p1/smtpd/filter.c opensmtpd-5.7.3p1/smtpd/filter.c
--- opensmtpd-5.7.2p1/smtpd/filter.c        2015-10-02 01:46:21.000000000 +0200
+++ opensmtpd-5.7.3p1/smtpd/filter.c        2015-10-05 13:33:41.000000000 +0200
@@ -728,7 +728,6 @@
         struct filter_session        *s = io->arg;
         size_t                         len, n;
         char                        *data;
-        char                        buf[65535];

         log_trace(TRACE_FILTERS, "filter: filter_tx_io(%p, %s)", s,
io_strevent(evt));

@@ -736,10 +735,9 @@
         case IO_DATAIN:
                 data = iobuf_data(&s->ibuf);
                 len = iobuf_len(&s->ibuf);
-                memmove(buf, data, len);
-                buf[len] = 0;
-                log_trace(TRACE_FILTERS, "filter: filter_tx_io:
datain (%zu) for req %016"PRIx64": %s",
-                    len, s->id, buf);
+
+                log_trace(TRACE_FILTERS, "filter: filter_tx_io:
datain (%zu) for req %016"PRIx64"",
+                    len, s->id);

                 n = fwrite(data, 1, len, s->ofile);
                 if (n != len) {
diff -ru opensmtpd-5.7.2p1/smtpd/mda.c opensmtpd-5.7.3p1/smtpd/mda.c
--- opensmtpd-5.7.2p1/smtpd/mda.c        2015-10-02 01:46:21.000000000 +0200
+++ opensmtpd-5.7.3p1/smtpd/mda.c        2015-10-05 13:33:41.000000000 +0200
@@ -286,8 +286,17 @@
                                 deliver.userinfo = *userinfo;
                                 (void)strlcpy(deliver.user, userinfo->username,
                                     sizeof(deliver.user));
-                                (void)strlcpy(deliver.to, e->buffer,
-                                    sizeof(deliver.to));
+                                if (strlcpy(deliver.to, e->buffer,
+                                        sizeof(deliver.to))
+                                    >= sizeof(deliver.to)) {
+                                        mda_queue_tempfail(e->id,
+                                            "mda command too long",
+                                            ESC_OTHER_MAIL_SYSTEM_STATUS);
+                                        mda_log(e, "TempFail",
+                                            "mda command too long");
+                                        mda_done(s);
+                                        return;
+                                }
                                 break;

                         case A_MBOX:
diff -ru opensmtpd-5.7.2p1/smtpd/smtpd.c opensmtpd-5.7.3p1/smtpd/smtpd.c
--- opensmtpd-5.7.2p1/smtpd/smtpd.c        2015-10-02 01:46:21.000000000 +0200
+++ opensmtpd-5.7.3p1/smtpd/smtpd.c        2015-10-05 13:33:41.000000000 +0200
@@ -50,9 +50,11 @@
 #include <errno.h>
 #include <event.h>
 #include <fcntl.h>
+#include <fts.h>
 #include <grp.h> /* needed for setgroups */
 #include <imsg.h>
 #include <inttypes.h>
+#include <libgen.h>
 #ifdef HAVE_LOGIN_CAP_H
 #include <login_cap.h>
 #endif
@@ -440,8 +442,10 @@
                                             "couldn't enqueue offline "
                                             "message %s; smtpctl %s",
                                             child->path, cause);
-                                else
+                                else {
                                         unlink(child->path);
+                                        rmdir(dirname(child->path));
+                                }
                                 free(child->path);
                                 offline_done();
                                 break;
@@ -1075,28 +1079,43 @@
 static void
 offline_scan(int fd, short ev, void *arg)
 {
-        DIR                *dir = arg;
-        struct dirent        *d;
+        char                *path_argv[2];
+        FTS                *fts = arg;
+        FTSENT                *e;
         int                 n = 0;

-        if (dir == NULL) {
+        path_argv[0] = PATH_SPOOL PATH_OFFLINE;
+        path_argv[1] = NULL;
+
+        if (fts == NULL) {
                 log_debug("debug: smtpd: scanning offline queue...");
-                if ((dir = opendir(PATH_SPOOL PATH_OFFLINE)) == NULL)
-                        errx(1, "smtpd: opendir");
+                fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
+                if (fts == NULL) {
+                        log_warn("fts_open: %s", path_argv[0]);
+                        return;
+                }
         }

-        while ((d = readdir(dir)) != NULL) {
-                if (d->d_type != DT_REG)
+        while ((e = fts_read(fts)) != NULL) {
+                if (e->fts_info != FTS_F)
+                        continue;
+
+                /* offline files must be at depth 2 */
+                if (e->fts_level != 2)
+                        continue;
+
+                /* offline file owner must match parent directory owner */
+                if (e->fts_statp->st_uid != e->fts_parent->fts_statp->st_uid)
                         continue;

-                if (offline_add(d->d_name)) {
+                if (offline_add(e->fts_accpath)) {
                         log_warnx("warn: smtpd: "
-                            "could not add offline message %s", d->d_name);
+                            "could not add offline message %s", e->fts_name);
                         continue;
                 }

                 if ((n++) == OFFLINE_READMAX) {
-                        evtimer_set(&offline_ev, offline_scan, dir);
+                        evtimer_set(&offline_ev, offline_scan, fts);
                         offline_timeout.tv_sec = 0;
                         offline_timeout.tv_usec = 100000;
                         evtimer_add(&offline_ev, &offline_timeout);
@@ -1105,24 +1124,19 @@
         }

         log_debug("debug: smtpd: offline scanning done");
-        closedir(dir);
+        fts_close(fts);
 }

 static int
 offline_enqueue(char *name)
 {
-        char                 t[PATH_MAX], *path;
+        char                *path;
         struct stat         sb;
         pid_t                 pid;
         struct child        *child;
         struct passwd        *pw;

-        if (!bsnprintf(t, sizeof t, "%s/%s", PATH_SPOOL PATH_OFFLINE, name)) {
-                log_warnx("warn: smtpd: path name too long");
-                return (-1);
-        }
-
-        if ((path = strdup(t)) == NULL) {
+        if ((path = strdup(name)) == NULL) {
                 log_warn("warn: smtpd: strdup");
                 return (-1);
         }
diff -ru opensmtpd-5.7.2p1/smtpd/smtpd.h opensmtpd-5.7.3p1/smtpd/smtpd.h
--- opensmtpd-5.7.2p1/smtpd/smtpd.h        2015-10-02 01:46:21.000000000 +0200
+++ opensmtpd-5.7.3p1/smtpd/smtpd.h        2015-10-05 13:33:41.000000000 +0200
@@ -67,7 +67,7 @@
 #ifndef SMTPD_NAME
 #define        SMTPD_NAME                 "OpenSMTPD"
 #endif
-#define        SMTPD_VERSION                 "5.7.2p1"
+#define        SMTPD_VERSION                 "5.7.3p1"
 #define SMTPD_SESSION_TIMEOUT         300
 #define SMTPD_BACKLOG                 5

@@ -684,7 +684,7 @@
 };

 struct deliver {
-        char                        to[SMTPD_MAXMAILADDRSIZE];
+        char                        to[EXPAND_BUFFER];
         char                        from[SMTPD_MAXMAILADDRSIZE];
         char                        dest[SMTPD_MAXMAILADDRSIZE];
         char                        user[SMTPD_VUSERNAME_SIZE];


Current thread: