File: 0002-SECURITY-Fix-DoS-on-overly-long-input-from-Postfix.patch

package info (click to toggle)
postsrsd 1.10-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 396 kB
  • sloc: ansic: 1,613; sh: 249; makefile: 25
file content (211 lines) | stat: -rw-r--r-- 6,977 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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.

Origin: https://github.com/roehling/postsrsd/commit/077be98d8c8a9847e4ae0c7dc09e7474cbe27db2
Forwarded: not-needed
Last-Update: 2021-07-14
---
 postsrsd.c              | 52 ++++++++++++++++++++++++++++++-------------------
 run_postsrsd_tests.bats | 40 +++++++++++++++++++++++++++++++++----
 2 files changed, 68 insertions(+), 24 deletions(-)

diff --git a/postsrsd.c b/postsrsd.c
index c009d8f..5ebf7f6 100644
--- a/postsrsd.c
+++ b/postsrsd.c
@@ -518,9 +518,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) {
@@ -540,41 +540,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) {
-              fprintf (fp, "500 Invalid request\n");
-              fflush(fp);
+              fprintf (fp_write, "500 Invalid request\n");
+              fflush(fp_write);
               return EXIT_FAILURE;
             }
-            handler[sc](srs, fp, key, domain, excludes);
-            fflush (fp);
-            if (poll(fds, 1, timeout * 1000) <= 0) break;
-            line = fgets(linebuf, sizeof(linebuf), fp);
+            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);
       }
diff --git a/run_postsrsd_tests.bats b/run_postsrsd_tests.bats
index f4b04bb..3d52a50 100755
--- a/run_postsrsd_tests.bats
+++ b/run_postsrsd_tests.bats
@@ -2,9 +2,9 @@
 # vim: filetype=bash:
 
 if [ ! -x "$POSTSRSD" ]
 then
-	for builddir in . build* obj*
+	for builddir in . build* obj* _build*
 	do
 		if [ -x "${builddir}/postsrsd" ]
 		then
 			POSTSRSD="${builddir}/postsrsd"
@@ -14,9 +14,9 @@ then
 fi
 if [ ! -x "$POSTSRSD" ]
 then
 	cat>&2 <<- EOF
-	cannot find postsrsd executable (looked in ., build*, obj*)
+	cannot find postsrsd executable (looked in ., build*, obj*, _build*)
 	please build the executable first, or set the POSTSRSD
 	environment variable if it is in a different location.
 
 	EOF
@@ -25,14 +25,21 @@ fi
 
 LANG=C.UTF-8
 
 
+fillchar()
+{
+        local count="$1"
+        local char="$2"
+        eval 'printf "'"$char"'%.0s" {1..'"$count"'}'
+}
+
 start_postsrsd_at()
 {
 	echo 'tops3cr3t' > "$BATS_TMPDIR/postsrsd.secret"
 	local faketime="$1"
 	shift
-	faketime "${faketime}" ${POSTSRSD} -D -f 10001 -r 10002 -p "$BATS_TMPDIR/postsrsd.pid" -s "$BATS_TMPDIR/postsrsd.secret" -d example.com "$@"
+	faketime "${faketime}" ${POSTSRSD} -D -t1 -f 10001 -r 10002 -p "$BATS_TMPDIR/postsrsd.pid" -s "$BATS_TMPDIR/postsrsd.secret" -d example.com "$@"
 }
 
 stop_postsrsd()
 {
@@ -158,9 +165,9 @@ teardown()
 	read<&9 line
 	[[ "$line" =~ ^"500 Domain excluded" ]]
 }
 
-@test "SRS invalid requests" {
+@test "Malformed or invalid requests" {
 	start_postsrsd_at "2020-01-01 00:01:00 UTC"
 	exec 9<>/dev/tcp/127.0.0.1/10001
 	echo>&9 "get"
 	read<&9 line
@@ -172,5 +179,30 @@ teardown()
 	exec 9<>/dev/tcp/127.0.0.1/10001
 	echo>&9 "get encoding%error@otherdomain.com"
 	read<&9 line
 	[[ "$line" =~ ^500 ]]
+	exec 9<>/dev/tcp/127.0.0.1/10001
+	# Try to overflow the input buffer
+	echo>&9 "get too_long@`fillchar 1024 a`.com"
+	read<&9 line
+	[[ "$line" =~ ^500 ]]
+}
+
+@test "Pipelining multiple requests" {
+	start_postsrsd_at "2020-01-01 00:01:00 UTC"
+	exec 9<>/dev/tcp/127.0.0.1/10001
+	# Send two requests at once and see if PostSRSd answers both
+	echo>&9 -e "get test@domain1.com\nget test@domain2.com"
+	read<&9 line
+	[[ "$line" =~ ^200 ]]
+	read<&9 line
+	[[ "$line" =~ ^200 ]]
+}
+
+@test "Session timeout" {
+	start_postsrsd_at "2020-01-01 00:01:00 UTC"
+	exec 9<>/dev/tcp/127.0.0.1/10001
+	# Wait until PostSRSd disconnects due to inactivity
+	sleep 2
+	echo >&9 "get test@example.com"
+	! read <&9 line
 }