VYPR
High severityNVD Advisory· Published Mar 13, 2026· Updated Mar 13, 2026

Yamux remote Panic via malformed WindowUpdate credit

CVE-2026-31814

Description

Yamux is a stream multiplexer over reliable, ordered connections such as TCP/IP. From 0.13.0 to before 0.13.9, a specially crafted WindowUpdate can cause arithmetic overflow in send-window accounting, which triggers a panic in the connection state machine. This is remotely reachable over a normal network connection and does not require authentication. This vulnerability is fixed in 0.13.9.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Oversized WindowUpdate frames cause an arithmetic overflow in Yamux stream multiplexer, leading to a remotely exploitable panic before v0.13.9.

Vulnerability

Overview

Yamux, a stream multiplexer for reliable ordered connections like TCP/IP, contains an arithmetic overflow vulnerability in its send-window accounting logic. Versions 0.13.0 through 0.13.8 are affected. The bug is triggered by a specially crafted WindowUpdate frame; when processing such a frame, the internal send-window tracking can overflow, causing the connection state machine to panic [1][2].

Exploitation

An attacker can exploit this vulnerability over a normal network connection without any authentication. The attack is remotely reachable—no special privileges or prior access are required. The malicious actor simply sends a crafted WindowUpdate frame to an open Yamux connection, which is typically used in peer-to-peer networking scenarios such as libp2p [2][4].

Impact

When the panic occurs, the affected Yamux connection is abruptly terminated. This results in a denial of service for any application relying on that multiplexed stream connection. Since the panic happens in the connection state machine, it may also disrupt other streams multiplexed over the same connection, leading to cascading failures in the application [1][2].

Mitigation

The vulnerability is fixed in version 0.13.9. The fix improves flow-control credit verification for window updates, ensuring that arithmetic overflow is prevented before it can cause a panic [1][2][3]. Users should update to the patched version immediately. No workarounds have been publicly documented beyond applying the update [2].

AI Insight generated on May 18, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
yamuxcrates.io
>= 0.13.0, < 0.13.90.13.9

Affected products

2
  • Yamux/Yamuxllm-fuzzy
    Range: >=0.13.0 <0.13.9
  • libp2p/rust-yamuxv5
    Range: >= 0.13.0, < 0.13.9

Patches

1
b1aae09d60c0

feat: improve flow-control credit verification for window updates (#221)

https://github.com/libp2p/rust-yamuxJoão OliveiraFeb 27, 2026via ghsa
4 files changed · +49 40
  • yamux/src/connection.rs+22 12 modified
    @@ -615,14 +615,6 @@ impl<T: AsyncRead + AsyncWrite + Unpin> Active<T> {
                     log::error!("{}: invalid stream id {}", self.id, stream_id);
                     return Action::Terminate(Frame::protocol_error());
                 }
    -            if frame.body().len() > DEFAULT_CREDIT as usize {
    -                log::error!(
    -                    "{}/{}: 1st body of stream exceeds default credit",
    -                    self.id,
    -                    stream_id
    -                );
    -                return Action::Terminate(Frame::protocol_error());
    -            }
                 if self.streams.contains_key(&stream_id) {
                     log::error!("{}/{}: stream already exists", self.id, stream_id);
                     return Action::Terminate(Frame::protocol_error());
    @@ -637,7 +629,15 @@ impl<T: AsyncRead + AsyncWrite + Unpin> Active<T> {
                     if is_finish {
                         shared.update_state(self.id, stream_id, State::RecvClosed);
                     }
    -                shared.consume_receive_window(frame.body_len());
    +                if let Err(_err) = shared.consume_receive_window(frame.body_len()) {
    +                    log::error!(
    +                        "{}/{}: 1st body of stream exceeds default credit",
    +                        self.id,
    +                        stream_id
    +                    );
    +
    +                    return Action::Terminate(Frame::protocol_error());
    +                }
                     shared.buffer.push(frame.into_body());
                 }
                 self.streams.insert(stream_id, stream.clone_shared());
    @@ -646,18 +646,21 @@ impl<T: AsyncRead + AsyncWrite + Unpin> Active<T> {
     
             if let Some(s) = self.streams.get_mut(&stream_id) {
                 let mut shared = s.lock();
    -            if frame.body_len() > shared.receive_window() {
    +
    +            if let Err(_err) = shared.consume_receive_window(frame.body_len()) {
                     log::error!(
                         "{}/{}: frame body larger than window of stream",
                         self.id,
                         stream_id
                     );
    +
                     return Action::Terminate(Frame::protocol_error());
                 }
    +
                 if is_finish {
                     shared.update_state(self.id, stream_id, State::RecvClosed);
                 }
    -            shared.consume_receive_window(frame.body_len());
    +
                 shared.buffer.push(frame.into_body());
                 if let Some(w) = shared.reader.take() {
                     w.wake()
    @@ -730,7 +733,14 @@ impl<T: AsyncRead + AsyncWrite + Unpin> Active<T> {
     
             if let Some(s) = self.streams.get_mut(&stream_id) {
                 let mut shared = s.lock();
    -            shared.increase_send_window_by(frame.header().credit());
    +            if let Err(err) = shared.increase_send_window_by(frame.header().credit()) {
    +                log::error!(
    +                    "{}/{}: could not increase the send window, {err}",
    +                    self.id,
    +                    stream_id
    +                );
    +                return Action::Terminate(Frame::protocol_error());
    +            }
                 if is_finish {
                     shared.update_state(self.id, stream_id, State::RecvClosed);
     
    
  • yamux/src/connection/stream/flow_control.rs+10 11 modified
    @@ -3,7 +3,7 @@ use std::{cmp, sync::Arc};
     use parking_lot::Mutex;
     use web_time::Instant;
     
    -use crate::{connection::rtt::Rtt, Config, DEFAULT_CREDIT};
    +use crate::{connection::rtt::Rtt, Config, ConnectionError, DEFAULT_CREDIT};
     
     #[derive(Debug)]
     pub(crate) struct FlowController {
    @@ -169,29 +169,28 @@ impl FlowController {
             self.send_window
         }
     
    -    pub(crate) fn consume_send_window(&mut self, i: u32) {
    +    pub(crate) fn consume_send_window(&mut self, i: u32) -> Result<(), ConnectionError> {
             self.send_window = self
                 .send_window
                 .checked_sub(i)
    -            .expect("not exceed send window");
    +            .ok_or(ConnectionError::InvalidWindowUpdate)?;
    +        Ok(())
         }
     
    -    pub(crate) fn increase_send_window_by(&mut self, i: u32) {
    +    pub(crate) fn increase_send_window_by(&mut self, i: u32) -> Result<(), ConnectionError> {
             self.send_window = self
                 .send_window
                 .checked_add(i)
    -            .expect("send window not to exceed u32");
    +            .ok_or(ConnectionError::InvalidWindowUpdate)?;
    +        Ok(())
         }
     
    -    pub(crate) fn receive_window(&self) -> u32 {
    -        self.receive_window
    -    }
    -
    -    pub(crate) fn consume_receive_window(&mut self, i: u32) {
    +    pub(crate) fn consume_receive_window(&mut self, i: u32) -> Result<(), ConnectionError> {
             self.receive_window = self
                 .receive_window
                 .checked_sub(i)
    -            .expect("not exceed receive window");
    +            .ok_or(ConnectionError::InvalidWindowUpdate)?;
    +        Ok(())
         }
     }
     
    
  • yamux/src/connection/stream.rs+10 17 modified
    @@ -10,6 +10,7 @@
     
     use crate::connection::rtt::Rtt;
     use crate::frame::header::ACK;
    +use crate::ConnectionError;
     use crate::{
         chunks::Chunks,
         connection::{self, rtt, StreamCommand},
    @@ -372,16 +373,12 @@ impl AsyncWrite for Stream {
                     shared.writer = Some(cx.waker().clone());
                     return Poll::Pending;
                 }
    -            let k = std::cmp::min(
    -                shared.send_window(),
    -                buf.len().try_into().unwrap_or(u32::MAX),
    -            );
    -            let k = std::cmp::min(
    -                k,
    -                self.config.split_send_size.try_into().unwrap_or(u32::MAX),
    -            );
    -            shared.consume_send_window(k);
    -            Vec::from(&buf[..k as usize])
    +            let k = std::cmp::min(shared.send_window() as usize, buf.len());
    +            let k = std::cmp::min(k, self.config.split_send_size);
    +            shared
    +                .consume_send_window(k as u32)
    +                .expect("not exceed receive window");
    +            Vec::from(&buf[..k])
             };
             let n = body.len();
             let mut frame = Frame::data(self.id, body).expect("body <= u32::MAX").left();
    @@ -527,19 +524,15 @@ impl Shared {
             self.flow_controller.send_window()
         }
     
    -    pub(crate) fn consume_send_window(&mut self, i: u32) {
    +    pub(crate) fn consume_send_window(&mut self, i: u32) -> Result<(), ConnectionError> {
             self.flow_controller.consume_send_window(i)
         }
     
    -    pub(crate) fn increase_send_window_by(&mut self, i: u32) {
    +    pub(crate) fn increase_send_window_by(&mut self, i: u32) -> Result<(), ConnectionError> {
             self.flow_controller.increase_send_window_by(i)
         }
     
    -    pub(crate) fn receive_window(&self) -> u32 {
    -        self.flow_controller.receive_window()
    -    }
    -
    -    pub(crate) fn consume_receive_window(&mut self, i: u32) {
    +    pub(crate) fn consume_receive_window(&mut self, i: u32) -> Result<(), ConnectionError> {
             self.flow_controller.consume_receive_window(i)
         }
     }
    
  • yamux/src/error.rs+7 0 modified
    @@ -24,6 +24,9 @@ pub enum ConnectionError {
         Closed,
         /// Too many streams are open, so no further ones can be opened at this time.
         TooManyStreams,
    +    /// A window update operation was rejected because the supplied credit
    +    /// is invalid for the current flow-control window (e.g. overflow).
    +    InvalidWindowUpdate,
     }
     
     impl std::fmt::Display for ConnectionError {
    @@ -36,6 +39,9 @@ impl std::fmt::Display for ConnectionError {
                 }
                 ConnectionError::Closed => f.write_str("connection is closed"),
                 ConnectionError::TooManyStreams => f.write_str("maximum number of streams reached"),
    +            ConnectionError::InvalidWindowUpdate => {
    +                f.write_str("invalid window update for the current flow control window")
    +            }
             }
         }
     }
    @@ -48,6 +54,7 @@ impl std::error::Error for ConnectionError {
                 ConnectionError::NoMoreStreamIds
                 | ConnectionError::Closed
                 | ConnectionError::TooManyStreams => None,
    +            ConnectionError::InvalidWindowUpdate => None,
             }
         }
     }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.