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.
* 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
*
* 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;
/* 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); \
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 \
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);
* 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),
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) ?
* 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);
}
* 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);
#ifndef NO_SHARED_EMPTY_STRINGS
if (ptr == empty_string)
return (mymalloc(len));
+ if (len == 0) {
+ myfree(ptr);
+ return (mymalloc(0));
+ }
#endif
/*
* 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;
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)
},
{"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,
/* Utility library. */
-#define VSTRING_INTERNAL
-
#include "mymalloc.h"
#include "msg.h"
#include "vbuf_print.h"
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);
}
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);
}
*/
#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
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;