]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.12-20260710
authorWietse Z Venema <wietse@porcupine.org>
Fri, 10 Jul 2026 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Mon, 13 Jul 2026 16:01:23 +0000 (02:01 +1000)
postfix/HISTORY
postfix/src/global/mail_version.h
postfix/src/qmqpd/qmqpd.c
postfix/src/smtp/smtp_proto.c
postfix/src/smtp/smtp_sasl_auth_cache.c
postfix/src/tlsmgr/tlsmgr.c
postfix/src/util/mymalloc.c
postfix/src/util/mymalloc_test.c
postfix/src/util/vstream.c
postfix/src/util/vstring.h
postfix/src/xsasl/xsasl_dovecot_server.c

index c22b0b2f793f3552a554a9128af56d4402878e69..0445d7a1aadfa31ba4b613fc93a92f3db6625057 100644 (file)
@@ -31653,6 +31653,41 @@ Apologies for any names omitted.
        was used in test code. Reported by Qualys, assisted by
        Claude Mythos Preview. Files: dict.h, dict.c.
 
+20260710
+
+       Code hygiene: the unsafe macro VSTRING_AT_OFFSET() was
+       called with an argument containing the unsafe macro
+       VSTRING_LEN(). This under-estimated the amount of available
+       buffer space (no harm done). Reported by Qualys (not assisted
+       by AI). Files: vstream.c, vstring.h.
+
+       Code hygiene: do not reply with stale data after an error
+       receiving a tlsmgr(8) request. Reported by Qualys, assisted
+       by Claude Mythos Preview. File: tlsmgr.c.
+
+       Code hygiene: in the SMTP client protocol engine, evaluate
+       the RETURN() macro argument before freeing resources.
+       Reported by Qualys, assisted by Claude Mythos Preview. File:
+       smtp_proto.c.
+
+       Code hygiene: myrealloc(ptr, 0) still resulted in a panic.
+       Reported by Qualys, assisted by Claude Mythos Preview.
+       Files: mymalloc.c, mymalloc_test.c.
+
+       Code hygiene: worst-case memory allocation for the
+       smtp_sasl_auth_cache reader. Suggested by Qualys, assisted
+       by Claude Mythos Preview. File: smtp_sasl_auth_cache.c.
+
+       Bug (defect introduced: Postfix 2.4, date: 20051222): null
+       pointer read crash while parsing a malformed Dovecot AUTH
+       server response. Reported by Qualys, assisted by Claude
+       Mythos Preview. File: xsasl_dovecot_server.c.
+
+       Code hygiene: in the qmqpd daemon, explicitly null-terminate
+       message content to prevent strncmp() from reading up to 5
+       bytes of unused VSTRING buffer space. Suggested by Qualys,
+       assisted by Claude Mythos Preview. File: qmqpd.c.
+
 TODO
 
        Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc.
index 8dcd307805b6d3cbfe0210fb6ba9356ae81a6ad8..b516b12cbb5b46767c92d69c249e8daa882c6561 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20260706"
+#define MAIL_RELEASE_DATE      "20260710"
 #define MAIL_VERSION_NUMBER    "3.12"
 
 #ifdef SNAPSHOT
index 2214d9855203c0e4a2674bc4e64e294bcad2cc6d..815d1e4eba7d0c36732efa011bd6722a3dfb45f6 100644 (file)
@@ -493,7 +493,10 @@ static void qmqpd_write_content(QMQPD_STATE *state)
      * 
      * XXX Deal with UNIX-style From_ lines at the start of message content just
      * in case.
+     * 
+     * 202607 Qualys+Mythos: strncmp() requires null-terminated input.
      */
+    VSTRING_TERMINATE(state->message);
     for (next = STR(state->message); /* void */ ; /* void */ ) {
        if ((ch = qmqpd_next_line(state->message, &start, &len, &next)) < 0)
            break;
index 4499087b67b50a292b97758cb9543e107ee09411..6ad8f6497edd1c1fc305c1c25e288941f2df796a 100644 (file)
@@ -1730,7 +1730,10 @@ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
 
     /* Caution: changes to RETURN() also affect code outside the main loop. */
 
+    /* 202607 Qualys+Mythos: evaluate argument before freeing resources. */
+
 #define RETURN(x) do { \
+       int _rv = (x); \
        if (recv_state != SMTP_STATE_LAST) \
            DONT_CACHE_THIS_SESSION; \
        vstring_free(next_command); \
@@ -1738,7 +1741,7 @@ static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state,
            myfree((void *) survivors); \
        if (session->mime_state) \
            session->mime_state = mime_state_free(session->mime_state); \
-       return (x); \
+       return (_rv); \
     } while (0)
 
 #define SENDER_IS_AHEAD \
index f3104ca818514b85172808c7b77db50281026283..6203bbdcb4a5e9de7bf8205d14277b25cbbb00a2 100644 (file)
@@ -199,7 +199,8 @@ static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE *auth_cache,
                                                    const char *entry,
                                                    const char *password)
 {
-    ssize_t len = strlen(entry);
+    /* 202607 Qualys+Mythos: reserve space for whole entry incl. terminator. */
+    ssize_t len = strlen(entry + 1);
     char   *cache_hash = mymalloc(len);
     char   *curr_hash;
     unsigned long now = (unsigned long) time((time_t *) 0);
index 59fc95eec9179daf7f414cf818ed89a80fc19ff9..e38c4dffa135bfcbfe8f79ace07e62debf457c19 100644 (file)
@@ -639,6 +639,7 @@ static void tlsmgr_service(VSTREAM *client_stream, char *unused_service,
         * Load session from cache.
         */
        if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) {
+           VSTRING_RESET(buffer);
            if (attr_scan(client_stream, ATTR_FLAG_STRICT,
                          RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type),
                          RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id),
@@ -649,13 +650,12 @@ static void tlsmgr_service(VSTREAM *client_stream, char *unused_service,
                if (ent->cache_label == 0) {
                    msg_warn("bogus cache type \"%s\" in \"%s\" request",
                             STR(cache_type), TLS_MGR_REQ_LOOKUP);
-                   VSTRING_RESET(buffer);
                } else if (ent->cache_info == 0) {
 
                    /*
                     * Cache type valid, but not enabled
                     */
-                   VSTRING_RESET(buffer);
+                    /* void */ ;
                } else {
                    status = tls_scache_lookup(ent->cache_info,
                                               STR(cache_id), buffer) ?
@@ -725,16 +725,15 @@ static void tlsmgr_service(VSTREAM *client_stream, char *unused_service,
         * RFC 5077 TLS session ticket keys
         */
        else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) {
+           VSTRING_RESET(buffer);
            if (attr_scan(client_stream, ATTR_FLAG_STRICT,
                          RECV_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, buffer),
                          ATTR_TYPE_END) == 1) {
                if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) {
                    msg_warn("invalid session ticket key name length: %ld",
                             (long) LEN(buffer));
-                   VSTRING_RESET(buffer);
                } else if (*smtpd_cache.cache_timeout <= 0) {
                    status = TLS_MGR_STAT_ERR;
-                   VSTRING_RESET(buffer);
                } else {
                    status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout);
                }
@@ -750,10 +749,10 @@ static void tlsmgr_service(VSTREAM *client_stream, char *unused_service,
         * Entropy request.
         */
        else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) {
+           VSTRING_RESET(buffer);
            if (attr_scan(client_stream, ATTR_FLAG_STRICT,
                          RECV_ATTR_INT(TLS_MGR_ATTR_SIZE, &len),
                          ATTR_TYPE_END) == 1) {
-               VSTRING_RESET(buffer);
                if (len <= 0 || len > 255) {
                    msg_warn("bogus seed length \"%d\" in \"%s\" request",
                             len, TLS_MGR_REQ_SEED);
index dbf0e496dd813d87b2a1c6e5b4edd4b9fc19ad67..d70affd83dfab819b4f6f1f9a620677ec38a9861 100644 (file)
@@ -197,6 +197,10 @@ void   *myrealloc(void *ptr, ssize_t len)
 #ifndef NO_SHARED_EMPTY_STRINGS
     if (ptr == empty_string)
        return (mymalloc(len));
+    if (len == 0) {
+       myfree(ptr);
+       return (mymalloc(0));
+    }
 #endif
 
     /*
@@ -204,7 +208,7 @@ void   *myrealloc(void *ptr, ssize_t len)
      * allows us to catch integer overflow problems that weren't already
      * caught up-stream.
      */
-    if (len < 1)
+    if (len < 0)
        msg_panic("myrealloc: requested length %ld", (long) len);
 #ifdef MYMALLOC_FUZZ
     len += MYMALLOC_FUZZ;
index 9e52067d37fcf1261bb76643088f2803b922b763..3de6866f1b8c6338a396c33357d8dce47a110fa6 100644 (file)
@@ -130,15 +130,35 @@ static void test_myrealloc_normal(PTEST_CTX *t, const PTEST_CASE *tp)
     myfree(ptr);
 }
 
+static void test_myrealloc_zero(PTEST_CTX *t, const PTEST_CASE *tp)
+{
+    void   *ptr;
+    void *got;
+    void *want;
+
+    ptr = mymalloc(100);
+    got = myrealloc(ptr, 0);
+    want = mymalloc(0);
+    if (got != want)
+       ptest_error(t, "myrealloc: zero length result: got %p, want %p",
+                   got, want);
+
+    /*
+     * myfree() is a NOOP.
+     */
+    myfree(want);
+    myfree(got);
+}
+
 static void test_myrealloc_panic_too_small(PTEST_CTX *t, const PTEST_CASE *tp)
 {
     void   *ptr;
 
-    expect_ptest_log_event(t, "panic: myrealloc: requested length 0");
+    expect_ptest_log_event(t, "panic: myrealloc: requested length -1");
     ptr = mymalloc(100);
     ptest_defer(t, myfree, ptr);
-    (void) myrealloc(ptr, 0);
-    ptest_fatal(t, "myrealloc(_, 0) returned");
+    (void) myrealloc(ptr, -1);
+    ptest_fatal(t, "myrealloc(_, -1) returned");
 }
 
 static void test_myrealloc_fatal_out_of_mem(PTEST_CTX *t, const PTEST_CASE *tp)
@@ -343,6 +363,8 @@ static const PTEST_CASE ptestcases[] = {
     },
     {"myrealloc + myfree normal case", test_myrealloc_normal,
     },
+    {"myrealloc static result for zero length", test_myrealloc_zero,
+    },
     {"myrealloc panic for too small request", test_myrealloc_panic_too_small,
     },
     {"myrealloc fatal for out of memory", test_myrealloc_fatal_out_of_mem,
index 136db9de05f2a6d163501b93275535d909c06adc..d380b751427aad23751c5347502f92049a41c386 100644 (file)
 
 /* Utility library. */
 
-#define VSTRING_INTERNAL
-
 #include "mymalloc.h"
 #include "msg.h"
 #include "vbuf_print.h"
@@ -1588,7 +1586,7 @@ ssize_t vstream_fread_buf(VSTREAM *fp, VSTRING *vp, ssize_t len)
     VSTRING_SPACE(vp, len);
     ret = vstream_fread(fp, vstring_str(vp), len);
     if (ret > 0)
-       VSTRING_AT_OFFSET(vp, ret);
+       vstring_set_payload_size(vp, ret);
     return (ret);
 }
 
@@ -1601,7 +1599,7 @@ ssize_t vstream_fread_app(VSTREAM *fp, VSTRING *vp, ssize_t len)
     VSTRING_SPACE(vp, len);
     ret = vstream_fread(fp, vstring_end(vp), len);
     if (ret > 0)
-       VSTRING_AT_OFFSET(vp, VSTRING_LEN(vp) + ret);
+       vstring_set_payload_size(vp, VSTRING_LEN(vp) + ret);
     return (ret);
 }
 
index c478d02af4203df2d6027a013b3fcd7e7891eca8..80045b24cfb20a81aa0db15e6dc85e1084a22646 100644 (file)
@@ -91,8 +91,10 @@ CHECK_VAL_HELPER_DCL(VSTRING_CTL, ssize_t);
   */
 #ifdef VSTRING_INTERNAL
 #define VSTRING_AT_OFFSET(vp, offset) do { \
-       (vp)->vbuf.ptr = (vp)->vbuf.data + (offset); \
-       (vp)->vbuf.cnt = (vp)->vbuf.len - (offset); \
+       VSTRING *__vp__ = (vp); \
+       ssize_t __offset__ = (offset); \
+       (__vp__)->vbuf.ptr = (__vp__)->vbuf.data + (__offset__); \
+       (__vp__)->vbuf.cnt = (__vp__)->vbuf.len - (__offset__); \
     } while (0)
 #endif
 
index e9c16466c1df2d252f5772995bec39e5dfefa99b..b93cd1bf9656cda241ee26ab13e4f6b1b539b278 100644 (file)
@@ -313,7 +313,7 @@ static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp)
        cmd = line;
        line = split_at(line, '\t');
 
-       if (strcmp(cmd, "VERSION") == 0) {
+       if (strcmp(cmd, "VERSION") == 0 && line != NULL) {
            if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) {
                msg_warn("SASL: Protocol version error");
                break;