Skip to content

fix: prevent reuse of the stream after an error #7014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

link2xt
Copy link
Collaborator

@link2xt link2xt commented Jul 18, 2025

When a stream timeouts, tokio_io_timeout::TimeoutStream returns an error once, but then allows to keep using the stream, e.g. calling poll_read() again.

This can be dangerous if the error is ignored.
For example in case of IMAP stream,
if IMAP command is sent,
but then reading the response
times out and the error is ignored,
it is possible to send another IMAP command.
In this case leftover response
from a previous command may be read
and interpreted as the response
to the new IMAP command.

ErrorCapturingStream wraps the stream
to prevent its reuse after an error.

@link2xt link2xt self-assigned this Jul 18, 2025
@Hocuri
Copy link
Collaborator

Hocuri commented Jul 18, 2025

I take it this PR is not ready for review? (I'm wondering because you mentioned it from the other PR)

@link2xt link2xt force-pushed the link2xt/error-capturing-stream branch 2 times, most recently from 204c889 to d0ec495 Compare July 19, 2025 11:44
@link2xt link2xt marked this pull request as ready for review July 19, 2025 11:45
@link2xt link2xt requested review from Hocuri and iequidoo July 19, 2025 11:45
@link2xt
Copy link
Collaborator Author

link2xt commented Jul 19, 2025

It's ready now.

@link2xt link2xt force-pushed the link2xt/error-capturing-stream branch from d0ec495 to d7bd4d0 Compare July 19, 2025 12:30
Copy link
Collaborator

@Hocuri Hocuri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fingers crossed 🤞 that this will help!

When a stream timeouts, `tokio_io_timeout::TimeoutStream`
returns an error once, but then allows to keep using
the stream, e.g. calling `poll_read()` again.

This can be dangerous if the error is ignored.
For example in case of IMAP stream,
if IMAP command is sent,
but then reading the response
times out and the error is ignored,
it is possible to send another IMAP command.
In this case leftover response
from a previous command may be read
and interpreted as the response
to the new IMAP command.

ErrorCapturingStream wraps the stream
to prevent its reuse after an error.
@link2xt link2xt force-pushed the link2xt/error-capturing-stream branch from d7bd4d0 to 3010d28 Compare July 19, 2025 13:44
@link2xt
Copy link
Collaborator Author

link2xt commented Jul 19, 2025

While this prevents reading more data after getting a low-level error, this unfortunately may not protect from misuse in case of additional buffering on top. After looking at the implementation of tokio BufReader it does not seem possible on a first glance to get a read error and then get access to the buffer by doing a read of different size, but we also have buffering inside the deflate decompressor used by IMAP stream and BufReader implementation may change in the future.


use crate::net::SessionStream;

/// Stream that remembers the first error
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only remembers whether an error took place. The comment should be fixed

/// If true, the stream has already returned an error once.
///
/// All read and write operations return error in this case.
is_broken: bool,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, separate flags is_{read,write}_broken may be introduced, though this is probably not necessary. Still, this way the reader may finish reading useful responses from the server even if writing breaks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we write some command, and writing breaks recoverably (e.g. times out) we don't want whatever response arrives to be read and misinterpreted as a response to the next command.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't be interpreted as a response to the next command because the next command can't even be written to the socket (because is_write_broken persists). But responses to the previous commands can be read, what is wrong with them? Usually protocols allow commands to be sent in batches and responses can be processed asynchronously

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants