aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS18
-rw-r--r--report.c138
2 files changed, 95 insertions, 61 deletions
diff --git a/NEWS b/NEWS
index 04239b16..67dc1f9e 100644
--- a/NEWS
+++ b/NEWS
@@ -82,6 +82,24 @@ removed from a 6.5.0 or newer release.)
server to test against. Use GSSAPI.
--------------------------------------------------------------------------------
+fetchmail-6.4.20 (not yet released):
+
+# SECURITY FIX:
+* When a log message exceeds c. 2 kByte in size, for instance, with very long
+ header contents, and depending on verbosity option, fetchmail can crash or
+ misreport each first log message that requires a buffer reallocation.
+ fetchmail then reallocates memory and re-runs vsnprintf() without another
+ call to va_start(), so it reads garbage. The exact impact depends on
+ many factors around the compiler and operating system configurations used and
+ the implementation details of the stdarg.h interfaces of the two functions
+ mentioned before. To fix CVE-2021-38386.
+
+ Reported by Christian Herdtweck of Intra2net AG, Tübingen, Germany.
+
+ He also offered a patch, which I could not take for fetchmail 6.4 because
+ it required a C99 system and I'd promised earlier that 6.4 would remain
+ compatible with C89 systems.
+--------------------------------------------------------------------------------
fetchmail-6.4.19 (released 2021-04-24, 30026 LoC):
# CHANGE:
diff --git a/report.c b/report.c
index 1466802a..aea6b3ea 100644
--- a/report.c
+++ b/report.c
@@ -44,6 +44,8 @@ static unsigned int partial_message_size = 0;
static unsigned int partial_message_size_used = 0;
static char *partial_message;
static int partial_suppress_tag = 0;
+/* default size for the allocation of the report buffer */
+const size_t defaultsize = 4096;
static unsigned unbuffered;
static unsigned int use_syslog;
@@ -177,6 +179,27 @@ void report_init(int mode /** 0: regular output, 1: unbuffered output, -1: syslo
}
}
+static void rep_ensuresize(size_t increment) {
+ if (partial_message_size == 0)
+ {
+ /* initialization */
+ partial_message_size_used = 0;
+ /* avoid too many small allocations initially */
+ if (increment < defaultsize) increment = defaultsize;
+ partial_message_size = increment;
+ partial_message = (char *)MALLOC (partial_message_size);
+ }
+ else /* already have buffer -> resize if too little room */
+ {
+ if (increment < defaultsize) increment = defaultsize;
+ if (partial_message_size - partial_message_size_used < increment)
+ {
+ partial_message_size += increment;
+ partial_message = (char *)REALLOC (partial_message, partial_message_size);
+ }
+ }
+}
+
/* Build an report message by appending MESSAGE, which is a printf-style
format string with optional args, to the existing report message (which may
be empty.) The completed report message is finally printed (and reset to
@@ -185,52 +208,37 @@ void report_init(int mode /** 0: regular output, 1: unbuffered output, -1: syslo
message exists, then, in an attempt to keep the messages in their proper
sequence, the partial message will be printed as-is (with a trailing
newline) before report() prints its message. */
+
+
/* VARARGS */
+#ifdef HAVE_STDARG_H
+static int report_vgetsize(const char *message, va_list args)
+{
+ char tmp[1];
-static void rep_ensuresize(void) {
- /* Make an initial guess for the size of any single message fragment. */
- if (partial_message_size == 0)
- {
- partial_message_size_used = 0;
- partial_message_size = 2048;
- partial_message = (char *)MALLOC (partial_message_size);
- }
- else
- if (partial_message_size - partial_message_size_used < 1024)
- {
- partial_message_size += 2048;
- partial_message = (char *)REALLOC (partial_message, partial_message_size);
- }
+ return vsnprintf(tmp, 1, message, args);
}
-#ifdef HAVE_STDARG_H
-static void report_vbuild(const char *message, va_list args)
+/* note that report_vbuild assumes that the buffer was already allocated. */
+/* VARARGS */
+static int report_vbuild(const char *message, va_list args)
{
int n;
- for ( ; ; )
- {
- /*
- * args has to be initialized before every call of vsnprintf(),
- * because vsnprintf() invokes va_arg macro and thus args is
- * undefined after the call.
- */
- n = vsnprintf (partial_message + partial_message_size_used, partial_message_size - partial_message_size_used,
- message, args);
-
- /* output error, f. i. EILSEQ */
- if (n < 0) break;
-
- if (n >= 0
- && (unsigned)n < partial_message_size - partial_message_size_used)
- {
- partial_message_size_used += n;
- break;
- }
+ n = vsnprintf (partial_message + partial_message_size_used,
+ partial_message_size - partial_message_size_used,
+ message, args);
+
+ /* output error, f. i. EILSEQ */
+ if (n < 0)
+ return -1;
- partial_message_size += 2048;
- partial_message = (char *)REALLOC (partial_message, partial_message_size);
+ if (n > 0)
+ {
+ partial_message_size_used += n;
}
+
+ return n;
}
#endif
@@ -243,40 +251,45 @@ report_build (FILE *errfp, message, va_alist)
va_dcl
#endif
{
+ int n;
#ifdef VA_START
va_list args;
-#else
- int n;
#endif
- rep_ensuresize();
+/* the logic is to first calculate the size,
+ * then reallocate, then fill the buffer
+ */
#if defined(VA_START)
VA_START(args, message);
- report_vbuild(message, args);
+ n = report_vgetsize(message, args);
va_end(args);
-#else
- for ( ; ; )
- {
- n = snprintf (partial_message + partial_message_size_used,
- partial_message_size - partial_message_size_used,
- message, a1, a2, a3, a4, a5, a6, a7, a8);
- /* output error, f. i. EILSEQ */
- if (n < 0) break;
+ rep_ensuresize(n + 1);
- if (n >= 0
- && (unsigned)n < partial_message_size - partial_message_size_used)
- {
- partial_message_size_used += n;
- break;
- }
+ VA_START(args, message);
+ n = report_vbuild(message, args);
+ va_end(args);
+#else
+ {
+ char tmp[1];
+ /* note that SUSv2 specifies that with the 2nd argument zero, an
+ * unspecified value less than 1 were to be returned. This is not
+ * useful, so pass 1. */
+ n = snprintf (tmp, 1,
+ message, a1, a2, a3, a4, a5, a6, a7, a8);
- partial_message_size += 2048;
- partial_message = REALLOC (partial_message, partial_message_size);
+ if (n > 0)
+ rep_ensuresize(n + 1);
}
+
+ n = snprintf (partial_message + partial_message_size_used,
+ partial_message_size - partial_message_size_used,
+ message, a1, a2, a3, a4, a5, a6, a7, a8);
#endif
+ if (n > 0) partial_message_size_used += n;
+
if (unbuffered && partial_message_size_used != 0)
{
partial_message_size_used = 0;
@@ -308,15 +321,18 @@ report_complete (FILE *errfp, message, va_alist)
va_dcl
#endif
{
+ int n;
#ifdef VA_START
va_list args;
-#endif
- rep_ensuresize();
+ VA_START(args, message);
+ n = report_vgetsize(message, args);
+ va_end(args);
+
+ rep_ensuresize(n + 1);
-#if defined(VA_START)
VA_START(args, message);
- report_vbuild(message, args);
+ n = report_vbuild(message, args);
va_end(args);
#else
report_build(errfp, message, a1, a2, a3, a4, a5, a6, a7, a8);