With Postfix 2.9 I'm hardening the Postfix SMTP server and client
implementations against exhaustion problems with byte-at-a-time
I/O, by enforcing a total time limit per SMTP line, instead of a
time limit per read(2) or write(2) system call.
This was already implemented in Postfix 2.8 postscreen. Without
time limits, an malicious client could tie up one postscreen session
for line_length_limit * postscreen_command_count_limit * smtpd_timeout
seconds. Even with stress-adaptive timeouts, this amounts to a
whopping 2048 * 20 * 10s = 4.7 days, which would not be acceptable.
With time limits, this reduces to postscreen_command_count_limit *
smtpd_timeout, or 200s with stress-adaptive timeouts.
I'm now adding similar deadline support to the Postfix TLS engine.
Below is a patch that naively implements read/write timeouts by
taking the per-line time limits for plaintext mail, and using the
same limits for TLS transactions.
This works fine for SMTP-over-TLS commands, as long as the sender
does not use overly-aggressive command pipelining; unfortunately
this approach is problematic for the DATA-over-TLS phase.
The issue is that with TLS, the DATA payload is sent in chunks of
up to 16 kbytes, unlike plaintext sessions, where the DATA payload
is typically sent in chunks of up to 1460-bytes. Given the way the
TLS protocol works, an entire 16 kbyte chunk must be received within
the time limit. Sending 16 kbyte takes a lot more time than sending
1460 bytes, and using the per-line time limit would certainly
penalize traffic across slow network links.
As you see, there is a lot of care for detail that goes into
maintaining Postfix.
Wietse
20110207
Cleanup: read/write deadline support for single_server TLS
applications (i.e. smtpd(8), smtp(8)). File: tls/tls_bio_ops.c.
diff --exclude=man --exclude=html --exclude=README_FILES --exclude=.indent.pro
--exclude=Makefile.in -r -c /var/tmp/postfix-2.9-20110207/src/tls/tls_bio_ops.c
./src/tls/tls_bio_ops.c
*** /var/tmp/postfix-2.9-20110207/src/tls/tls_bio_ops.c Fri Dec 17 19:40:59 2010
--- ./src/tls/tls_bio_ops.c Tue Feb 8 19:19:00 2011
***************
*** 103,108 ****
--- 103,121 ----
/* System library. */
#include <sys_defs.h>
+ #include <sys/time.h>
+
+ #ifndef timersub
+ /* res = a - b */
+ #define timersub(a, b, res) do { \
+ (res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+ if ((res)->tv_usec < 0) { \
+ (res)->tv_sec--; \
+ (res)->tv_usec += 1000000; \
+ } \
+ } while (0)
+ #endif
#ifdef USE_TLS
***************
*** 129,134 ****
--- 142,161 ----
int err;
int retval = 0;
int done;
+ struct timeval time_limit; /* initial time limit */
+ struct timeval time_left; /* amount of time left */
+ struct timeval time_entry; /* time of tls_bio() entry */
+ struct timeval time_now; /* time after SSL_mumble() call */
+ struct timeval time_elapsed; /* total elapsed time */
+
+ /*
+ * Deadline management is simpler than with VSTREAMs, because we don't
+ * need to decrement a per-stream time limit. We just work within the
+ * budget that is available for this tls_bio() call.
+ */
+ time_limit.tv_sec = timeout;
+ time_limit.tv_usec = 0;
+ GETTIMEOFDAY(&time_entry);
/*
* If necessary, retry the SSL handshake or read/write operation after
***************
*** 194,205 ****
done = 1;
break;
case SSL_ERROR_WANT_WRITE:
- if (write_wait(fd, timeout) < 0)
- return (-1); /* timeout error */
- break;
case SSL_ERROR_WANT_READ:
! if (read_wait(fd, timeout) < 0)
! return (-1); /* timeout error */
break;
/*
--- 221,242 ----
done = 1;
break;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
! GETTIMEOFDAY(&time_now);
! timersub(&time_now, &time_entry, &time_elapsed);
! timersub(&time_limit, &time_elapsed, &time_left);
! timeout = time_left.tv_sec + (time_left.tv_usec > 0);
! if (timeout <= 0) {
! errno = ETIMEDOUT;
! return (-1);
! }
! if (err == SSL_ERROR_WANT_WRITE) {
! if (write_wait(fd, timeout) < 0)
! return (-1); /* timeout error */
! } else {
! if (read_wait(fd, timeout) < 0)
! return (-1); /* timeout error */
! }
break;
/*