Description: CVE-2024-27305 - SMTP smuggling
 SMTP smuggling due to poor handling of
 non-standard line endings
Author: Dale Richards <dale@dalerichards.net>
Origin: upstream, https://github.com/aio-libs/aiosmtpd/commit/24b6c79c8921cf1800e27ca144f4f37023982bbb
Bug: https://github.com/aio-libs/aiosmtpd/security/advisories/GHSA-pr2m-px7j-xg65
Last-Update: 2024-06-07
---
This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
--- a/aiosmtpd/smtp.py
+++ b/aiosmtpd/smtp.py
@@ -86,7 +86,7 @@
 EMPTY_BARR = bytearray()
 EMPTYBYTES = b''
 MISSING = _Missing()
-NEWLINE = '\n'
+NEWLINE = '\r\n'
 VALID_AUTHMECH = re.compile(r"[A-Z0-9_-]+\Z")
 
 # https://tools.ietf.org/html/rfc3207.html#page-3
@@ -1375,9 +1375,10 @@
             # Since eof_received cancels this coroutine,
             # readuntil() can never raise asyncio.IncompleteReadError.
             try:
-                line: bytes = await self._reader.readuntil()
+                # https://datatracker.ietf.org/doc/html/rfc5321#section-2.3.8
+                line: bytes = await self._reader.readuntil(b'\r\n')
                 log.debug('DATA readline: %s', line)
-                assert line.endswith(b'\n')
+                assert line.endswith(b'\r\n')
             except asyncio.CancelledError:
                 # The connection got reset during the DATA command.
                 log.info('Connection lost during DATA')
@@ -1394,7 +1395,7 @@
                 data *= 0
                 # Drain the stream anyways
                 line = await self._reader.read(e.consumed)
-                assert not line.endswith(b'\n')
+                assert not line.endswith(b'\r\n')
             # A lone dot in a line signals the end of DATA.
             if not line_fragments and line == b'.\r\n':
                 break
@@ -1406,7 +1407,7 @@
                 # Discard data immediately to prevent memory pressure
                 data *= 0
             line_fragments.append(line)
-            if line.endswith(b'\n'):
+            if line.endswith(b'\r\n'):
                 # Record data only if state is "NOMINAL"
                 if state == _DataState.NOMINAL:
                     line = EMPTY_BARR.join(line_fragments)
