IMAP: Discard oversized non-synchronizing literals (fixes #2768)
This commit is contained in:
@@ -38,6 +38,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If
|
|||||||
- `SearchSnippet/get`: incorrect response structure.
|
- `SearchSnippet/get`: incorrect response structure.
|
||||||
- `Thread/changes`: emit a container delete when a thread becomes empty.
|
- `Thread/changes`: emit a container delete when a thread becomes empty.
|
||||||
- `VacationResponse/set`: incorrect singleton handling.
|
- `VacationResponse/set`: incorrect singleton handling.
|
||||||
|
- IMAP: Discard oversized non-synchronizing literals (#2768).
|
||||||
- DANE: Improper `TLSA` record validation (#2328).
|
- DANE: Improper `TLSA` record validation (#2328).
|
||||||
- OIDC: Add default domain name to groups that are not email addresses.
|
- OIDC: Add default domain name to groups that are not email addresses.
|
||||||
- RocksDB: Enable blob garbage collection to reclaim disk space from deleted blobs.
|
- RocksDB: Enable blob garbage collection to reclaim disk space from deleted blobs.
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ pub enum State {
|
|||||||
Literal { non_sync: bool },
|
Literal { non_sync: bool },
|
||||||
LiteralSeek { size: u32, non_sync: bool },
|
LiteralSeek { size: u32, non_sync: bool },
|
||||||
LiteralData { remaining: u32 },
|
LiteralData { remaining: u32 },
|
||||||
|
LiteralDiscard { remaining: u32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Receiver<T: CommandParser> {
|
pub struct Receiver<T: CommandParser> {
|
||||||
@@ -315,15 +316,18 @@ impl<T: CommandParser> Receiver<T> {
|
|||||||
let size = self.buf.as_str().parse::<u32>().map_err(|_| {
|
let size = self.buf.as_str().parse::<u32>().map_err(|_| {
|
||||||
self.error_reset("Literal size is not a valid number.")
|
self.error_reset("Literal size is not a valid number.")
|
||||||
})?;
|
})?;
|
||||||
if self.current_request_size + size as usize > self.max_request_size
|
let oversize = self.current_request_size + size as usize
|
||||||
{
|
> self.max_request_size;
|
||||||
|
if oversize && !non_sync {
|
||||||
return Err(self.error_reset(format_compact!(
|
return Err(self.error_reset(format_compact!(
|
||||||
"Literal exceeds the maximum request size of {} bytes.",
|
"Literal exceeds the maximum request size of {} bytes.",
|
||||||
self.max_request_size
|
self.max_request_size
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
self.state = State::LiteralSeek { size, non_sync };
|
self.state = State::LiteralSeek { size, non_sync };
|
||||||
self.buf.resize_buffer(size as usize);
|
if !oversize {
|
||||||
|
self.buf.resize_buffer(size as usize);
|
||||||
|
}
|
||||||
self.buf.clear();
|
self.buf.clear();
|
||||||
} else {
|
} else {
|
||||||
return Err(self.error_reset("Invalid empty literal."));
|
return Err(self.error_reset("Invalid empty literal."));
|
||||||
@@ -356,14 +360,21 @@ impl<T: CommandParser> Receiver<T> {
|
|||||||
}
|
}
|
||||||
State::LiteralSeek { size, non_sync } => {
|
State::LiteralSeek { size, non_sync } => {
|
||||||
if ch == b'\n' {
|
if ch == b'\n' {
|
||||||
if size > 0 {
|
if non_sync
|
||||||
|
&& self.current_request_size + size as usize > self.max_request_size
|
||||||
|
{
|
||||||
|
self.state = State::LiteralDiscard { remaining: size };
|
||||||
|
} else if size > 0 {
|
||||||
self.state = State::LiteralData { remaining: size };
|
self.state = State::LiteralData { remaining: size };
|
||||||
|
if !non_sync {
|
||||||
|
return Err(Error::NeedsLiteral { size });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.state = State::Argument { last_ch: b' ' };
|
self.state = State::Argument { last_ch: b' ' };
|
||||||
self.push_token(Token::Nil)?;
|
self.push_token(Token::Nil)?;
|
||||||
}
|
if !non_sync {
|
||||||
if !non_sync {
|
return Err(Error::NeedsLiteral { size });
|
||||||
return Err(Error::NeedsLiteral { size });
|
}
|
||||||
}
|
}
|
||||||
} else if !ch.is_ascii_whitespace() {
|
} else if !ch.is_ascii_whitespace() {
|
||||||
return Err(
|
return Err(
|
||||||
@@ -371,6 +382,18 @@ impl<T: CommandParser> Receiver<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
State::LiteralDiscard { remaining } => {
|
||||||
|
if remaining > 1 {
|
||||||
|
self.state = State::LiteralDiscard {
|
||||||
|
remaining: remaining - 1,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return Err(self.error_reset(format_compact!(
|
||||||
|
"Literal exceeds the maximum request size of {} bytes.",
|
||||||
|
self.max_request_size
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
State::LiteralData { remaining } => {
|
State::LiteralData { remaining } => {
|
||||||
// SAFETY: We checked the size before entering this state
|
// SAFETY: We checked the size before entering this state
|
||||||
self.buf.push_unchecked(ch);
|
self.buf.push_unchecked(ch);
|
||||||
@@ -1103,4 +1126,48 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn receiver_discard_oversized_non_sync_literal() {
|
||||||
|
for frames in [
|
||||||
|
vec![
|
||||||
|
"a1 APPEND inbox (\\Draft) {40+}\r\n",
|
||||||
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n",
|
||||||
|
"b2 NOOP\r\n",
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
"a1 APPEND inbox (\\Draft) {40+}\r\nAAAAAAAAAAAAAAAAAAAA",
|
||||||
|
"AAAAAAAAAAAAAAAAAAAA\r\nb2 NOOP\r\n",
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
"a1 APPEND inbox (\\Draft) {40+}\r\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\nb2 NOOP\r\n",
|
||||||
|
],
|
||||||
|
] {
|
||||||
|
let mut receiver = Receiver::<Command>::with_max_request_size(30);
|
||||||
|
let mut requests = Vec::new();
|
||||||
|
let mut errors = 0;
|
||||||
|
for frame in &frames {
|
||||||
|
let mut bytes = frame.as_bytes().iter();
|
||||||
|
loop {
|
||||||
|
match receiver.parse(&mut bytes) {
|
||||||
|
Ok(request) => requests.push(request),
|
||||||
|
Err(Error::NeedsMoreData | Error::NeedsLiteral { .. }) => break,
|
||||||
|
Err(Error::Error { .. }) => errors += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(errors, 1, "expected a single error for {:#?}", frames);
|
||||||
|
assert_eq!(
|
||||||
|
requests,
|
||||||
|
vec![Request {
|
||||||
|
tag: "b2".into(),
|
||||||
|
command: Command::Noop,
|
||||||
|
tokens: vec![],
|
||||||
|
}],
|
||||||
|
"connection did not resync for {:#?}",
|
||||||
|
frames
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ use common::network::SessionStream;
|
|||||||
use imap_proto::{
|
use imap_proto::{
|
||||||
Command, StatusResponse,
|
Command, StatusResponse,
|
||||||
protocol::{
|
protocol::{
|
||||||
ImapResponse, quoted_string,
|
ImapResponse,
|
||||||
capability::{Capability, Response},
|
capability::{Capability, Response},
|
||||||
|
quoted_string,
|
||||||
},
|
},
|
||||||
receiver::Request,
|
receiver::Request,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user