IMAP: Check buf size before pushing escape sequences within quoted literals

This commit is contained in:
mdecimus
2025-09-13 20:13:25 +02:00
parent 058208992a
commit a8e631e881
3 changed files with 14 additions and 11 deletions

View File

@@ -26,9 +26,9 @@ If you are upgrading from v0.11.x or v0.12.x, this version includes **breaking c
- IMAP: Include `administer` permission in ACL responses. - IMAP: Include `administer` permission in ACL responses.
- IMAP: Add owner rights to ACL get responses. - IMAP: Add owner rights to ACL get responses.
- IMAP: Do not auto-train Bayes when moving messages from Junk to Trash. - IMAP: Do not auto-train Bayes when moving messages from Junk to Trash.
- IMAP/ManageSieve: Increase maximum quoted argument size (fixes #2039). - IMAP/ManageSieve: Increase maximum quoted argument size (#2039).
- CalDAV: Limit recurrence expansions in calendar reports. - CalDAV: Limit recurrence expansions in calendar reports ([CVE-2025-59045](https://github.com/stalwartlabs/stalwart/security/advisories/GHSA-xv4r-q6gr-6pfg)).
- WebDAV: Do not fix percent encoding on WebDAV FS (closes #2036). - WebDAV: Do not fix percent encoding on WebDAV FS (#2036).
## [0.13.2] - 2025-07-28 ## [0.13.2] - 2025-07-28

View File

@@ -4,11 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/ */
use super::{ResponseCode, ResponseType};
use compact_str::{CompactString, format_compact};
use std::fmt::Display; use std::fmt::Display;
use compact_str::{CompactString, format_compact}; const QUOTED_ARG_MAX_LEN: usize = 4096;
use super::{ResponseCode, ResponseType};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Error { pub enum Error {
@@ -277,7 +277,7 @@ impl<T: CommandParser> Receiver<T> {
if !escaped { if !escaped {
self.push_argument(true)?; self.push_argument(true)?;
self.state = State::Argument { last_ch: b' ' }; self.state = State::Argument { last_ch: b' ' };
} else if self.buf.len() < 4096 { } else if self.buf.len() < QUOTED_ARG_MAX_LEN {
self.buf.push(ch); self.buf.push(ch);
self.state = State::ArgumentQuoted { escaped: false }; self.state = State::ArgumentQuoted { escaped: false };
} else { } else {
@@ -286,7 +286,11 @@ impl<T: CommandParser> Receiver<T> {
} }
b'\\' => { b'\\' => {
if escaped { if escaped {
self.buf.push(ch); if self.buf.len() < QUOTED_ARG_MAX_LEN {
self.buf.push(ch);
} else {
return Err(self.error_reset("Quoted argument too long."));
}
} }
self.state = State::ArgumentQuoted { escaped: !escaped }; self.state = State::ArgumentQuoted { escaped: !escaped };
} }
@@ -294,7 +298,7 @@ impl<T: CommandParser> Receiver<T> {
return Err(self.error_reset("Unterminated quoted argument.")); return Err(self.error_reset("Unterminated quoted argument."));
} }
_ => { _ => {
if self.buf.len() < 4096 { if self.buf.len() < QUOTED_ARG_MAX_LEN {
if escaped { if escaped {
self.buf.push(b'\\'); self.buf.push(b'\\');
} }
@@ -340,7 +344,6 @@ impl<T: CommandParser> Receiver<T> {
self.buf.push(ch); self.buf.push(ch);
} else { } else {
// Digit found after non-sync '+' flag // Digit found after non-sync '+' flag
return Err(self.error_reset("Invalid literal.")); return Err(self.error_reset("Invalid literal."));
} }
} }

View File

@@ -40,7 +40,7 @@ async fn limits() {
// Exceed max line length // Exceed max line length
let mut session = Session::test_with_shutdown(TestSMTP::from_core(core).server, rx); let mut session = Session::test_with_shutdown(TestSMTP::from_core(core).server, rx);
session.data.remote_ip_str = "10.0.0.1".into(); session.data.remote_ip_str = "10.0.0.1".into();
let mut buf = vec![b'A'; 2049]; let mut buf = vec![b'A'; 4097];
session.ingest(&buf).await.unwrap(); session.ingest(&buf).await.unwrap();
session.ingest(b"\r\n").await.unwrap(); session.ingest(b"\r\n").await.unwrap();
session.response().assert_code("554 5.3.4"); session.response().assert_code("554 5.3.4");