fix(swarm): prevent overflow in keep-alive computation

When adding a very large `Duration` to an `Instant`, an overflow can occur. To fix this, we check this before instantiating `Delay` and half the given duration until it no longer overflows.

Fixes: #4555.

Pull-Request: #4559.
This commit is contained in:
Thomas Eizinger 2023-09-27 17:38:03 +10:00 committed by GitHub
parent 38ea7ad453
commit 9cd0f3dbed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 43 additions and 11 deletions

2
Cargo.lock generated
View File

@ -3039,7 +3039,7 @@ dependencies = [
[[package]] [[package]]
name = "libp2p-swarm" name = "libp2p-swarm"
version = "0.43.4" version = "0.43.5"
dependencies = [ dependencies = [
"async-std", "async-std",
"either", "either",

View File

@ -100,7 +100,7 @@ libp2p-rendezvous = { version = "0.13.0", path = "protocols/rendezvous" }
libp2p-upnp = { version = "0.1.1", path = "protocols/upnp" } libp2p-upnp = { version = "0.1.1", path = "protocols/upnp" }
libp2p-request-response = { version = "0.25.1", path = "protocols/request-response" } libp2p-request-response = { version = "0.25.1", path = "protocols/request-response" }
libp2p-server = { version = "0.12.3", path = "misc/server" } libp2p-server = { version = "0.12.3", path = "misc/server" }
libp2p-swarm = { version = "0.43.4", path = "swarm" } libp2p-swarm = { version = "0.43.5", path = "swarm" }
libp2p-swarm-derive = { version = "0.33.0", path = "swarm-derive" } libp2p-swarm-derive = { version = "0.33.0", path = "swarm-derive" }
libp2p-swarm-test = { version = "0.2.0", path = "swarm-test" } libp2p-swarm-test = { version = "0.2.0", path = "swarm-test" }
libp2p-tcp = { version = "0.40.0", path = "transports/tcp" } libp2p-tcp = { version = "0.40.0", path = "transports/tcp" }

View File

@ -1,3 +1,8 @@
## 0.43.5
- Fix overflow in `KeepAlive` computation that could occur if `SwarmBuilder::idle_connection_timeout` is configured with `u64::MAX`.
See [PR 4559](https://github.com/libp2p/rust-libp2p/pull/4559).
## 0.43.4 ## 0.43.4
- Implement `Debug` for event structs. - Implement `Debug` for event structs.

View File

@ -3,7 +3,7 @@ name = "libp2p-swarm"
edition = "2021" edition = "2021"
rust-version = { workspace = true } rust-version = { workspace = true }
description = "The libp2p swarm" description = "The libp2p swarm"
version = "0.43.4" version = "0.43.5"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
license = "MIT" license = "MIT"
repository = "https://github.com/libp2p/rust-libp2p" repository = "https://github.com/libp2p/rust-libp2p"

View File

@ -361,15 +361,16 @@ where
} }
} }
(_, KeepAlive::Until(earliest_shutdown)) => { (_, KeepAlive::Until(earliest_shutdown)) => {
if let Some(requested_keep_alive) = let now = Instant::now();
earliest_shutdown.checked_duration_since(Instant::now())
{ if let Some(requested) = earliest_shutdown.checked_duration_since(now) {
let effective_keep_alive = max(requested_keep_alive, *idle_timeout); let effective_keep_alive = max(requested, *idle_timeout);
let safe_keep_alive = checked_add_fraction(now, effective_keep_alive);
// Important: We store the _original_ `Instant` given by the `ConnectionHandler` in the `Later` instance to ensure we can compare it in the above branch. // Important: We store the _original_ `Instant` given by the `ConnectionHandler` in the `Later` instance to ensure we can compare it in the above branch.
// This is quite subtle but will hopefully become simpler soon once `KeepAlive::Until` is fully deprecated. See <https://github.com/libp2p/rust-libp2p/issues/3844>/ // This is quite subtle but will hopefully become simpler soon once `KeepAlive::Until` is fully deprecated. See <https://github.com/libp2p/rust-libp2p/issues/3844>/
*shutdown = *shutdown = Shutdown::Later(Delay::new(safe_keep_alive), earliest_shutdown)
Shutdown::Later(Delay::new(effective_keep_alive), earliest_shutdown)
} }
} }
(_, KeepAlive::No) if idle_timeout == &Duration::ZERO => { (_, KeepAlive::No) if idle_timeout == &Duration::ZERO => {
@ -379,8 +380,10 @@ where
// Do nothing, i.e. let the shutdown timer continue to tick. // Do nothing, i.e. let the shutdown timer continue to tick.
} }
(_, KeepAlive::No) => { (_, KeepAlive::No) => {
let deadline = Instant::now() + *idle_timeout; let now = Instant::now();
*shutdown = Shutdown::Later(Delay::new(*idle_timeout), deadline); let safe_keep_alive = checked_add_fraction(now, *idle_timeout);
*shutdown = Shutdown::Later(Delay::new(safe_keep_alive), now + safe_keep_alive);
} }
(_, KeepAlive::Yes) => *shutdown = Shutdown::None, (_, KeepAlive::Yes) => *shutdown = Shutdown::None,
}; };
@ -479,6 +482,20 @@ fn gather_supported_protocols(handler: &impl ConnectionHandler) -> HashSet<Strea
.collect() .collect()
} }
/// Repeatedly halves and adds the [`Duration`] to the [`Instant`] until [`Instant::checked_add`] succeeds.
///
/// [`Instant`] depends on the underlying platform and has a limit of which points in time it can represent.
/// The [`Duration`] computed by the this function may not be the longest possible that we can add to `now` but it will work.
fn checked_add_fraction(start: Instant, mut duration: Duration) -> Duration {
while start.checked_add(duration).is_none() {
log::debug!("{start:?} + {duration:?} cannot be presented, halving duration");
duration /= 2;
}
duration
}
/// Borrowed information about an incoming connection currently being negotiated. /// Borrowed information about an incoming connection currently being negotiated.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub(crate) struct IncomingInfo<'a> { pub(crate) struct IncomingInfo<'a> {
@ -957,6 +974,16 @@ mod tests {
)); ));
} }
#[test]
fn checked_add_fraction_can_add_u64_max() {
let _ = env_logger::try_init();
let start = Instant::now();
let duration = checked_add_fraction(start, Duration::from_secs(u64::MAX));
assert!(start.checked_add(duration).is_some())
}
struct KeepAliveUntilConnectionHandler { struct KeepAliveUntilConnectionHandler {
until: Instant, until: Instant,
} }