From: =?utf-8?q?Timo_R=C3=B6hling?= <timo@gaussglocke.de>
Date: Sun, 21 Mar 2021 15:27:55 +0100
Subject: SECURITY: Fix DoS on overly long input from Postfix
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 8bit

Thanks to Mateusz Jończyk who reported this issue and gave valuable
feedback for its resolution.

PostSRSd would hang on an overly long GET request, because the
fread()/fwrite() logic in the subprocess would get confused by the
remaining input line in its buffer.

Theoretically, this error should never occur, as Postfix is supposed to
send valid email addresses only, which are shorter than the buffer, even
assuming every single character is percent-encoded. However, Postfix
sometimes does seem to send malformed request with multiple concatenated
email addresses. I'm not sure if there's a reliable way to trigger this
condition by an external attacker, but it is a security bug in PostSRSd
nevertheless.

Fixes CVE-2021-35525.

Tests not backported, as v1.5 did not have them yet.

Origin: https://github.com/roehling/postsrsd/commit/077be98d8c8a9847e4ae0c7dc09e7474cbe27db2
Forwarded: not-needed
Last-Update: 2021-07-14
---
 postsrsd.c | 54 +++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 35 insertions(+), 19 deletions(-)

diff --git a/postsrsd.c b/postsrsd.c
index a6fd118..64e1ad0 100644
--- a/postsrsd.c
+++ b/postsrsd.c
@@ -501,9 +501,9 @@ int main (int argc, char **argv)
     fds[sc].events = POLLIN;
   }
   while(TRUE) {
     int conn;
-    FILE *fp;
+    FILE *fp_read, *fp_write;
     char linebuf[1024], *line;
     char keybuf[1024], *key;
 
     if (poll(fds, socket_count, 1000) < 0) {
@@ -523,37 +523,53 @@ int main (int argc, char **argv)
           int i;
           // close listen sockets so that we don't stop the main daemon process from restarting
           for (i = 0; i < socket_count; ++i) close (sockets[i]);
 
-          fp = fdopen(conn, "r+");
-          if (fp == NULL) exit(EXIT_FAILURE);
-          fds[0].fd = conn;
-          fds[0].events = POLLIN;
-          if (poll(fds, 1, timeout * 1000) <= 0) return EXIT_FAILURE;
-          line = fgets(linebuf, sizeof(linebuf), fp);
-          while (line) {
-            fseek (fp, 0, SEEK_CUR); /* Workaround for Solaris */
+          /* create separate input/output streams */
+          fp_read = fdopen(conn, "r");
+          if (fp_read == NULL)
+            return EXIT_FAILURE;
+          fp_write = fdopen(dup(conn), "w");
+          if (fp_write == NULL) return EXIT_FAILURE;
+          errno = 0;
+          alarm(timeout);
+          if (errno != 0)
+              return EXIT_FAILURE;
+          while ((line = fgets(linebuf, sizeof(linebuf), fp_read))) {
             char* token;
+            alarm(0);
+            if (strlen(line) >= sizeof(linebuf) - 1) {
+              fprintf(fp_write, "500 Invalid request\n");
+              fflush(fp_write);
+              return EXIT_FAILURE;
+            }
             token = strtok(line, " \r\n");
             if (token == NULL || strcmp(token, "get") != 0) {
-              fprintf (fp, "500 Invalid request\n");
-              fflush (fp);
+              fprintf (fp_write, "500 Invalid request\n");
+              fflush (fp_write);
               return EXIT_FAILURE;
             }
             token = strtok(NULL, "\r\n");
             if (!token) {
-              fprintf (fp, "500 Invalid request\n");
-              fflush (fp);
+              fprintf (fp_write, "500 Invalid request\n");
+              fflush (fp_write);
               return EXIT_FAILURE;
             }
             key = url_decode(keybuf, sizeof(keybuf), token);
-            if (!key) break;
-            handler[sc](srs, fp, key, domain, excludes);
-            fflush (fp);
-            if (poll(fds, 1, timeout * 1000) <= 0) break;
-            line = fgets(linebuf, sizeof(linebuf), fp);
+            if (!key) {
+              fprintf (fp_write, "500 Invalid request\n");
+              fflush(fp_write);
+              return EXIT_FAILURE;
+            }
+            handler[sc](srs, fp_write, key, domain, excludes);
+            fflush (fp_write);
+            errno = 0;
+            alarm(timeout);
+            if (errno != 0)
+              return EXIT_FAILURE;
           }
-          fclose (fp);
+          fclose (fp_write);
+          fclose (fp_read);
           return EXIT_SUCCESS;
         }
         close (conn);
       }
