From cf7bbe45345b2c1311cece25fad1c7e4f1270c97 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 02:55:38 +0000 Subject: [PATCH] fix(deps): update yamux to 0.13.10, remove vulnerable 0.12.x (CVE-2026-32314) libp2p-yamux 0.47.0 depends on both yamux 0.12.x (legacy/backward-compat path, aliased as yamux012) and yamux 0.13.x (default path, aliased as yamux013). yamux 0.12.x is vulnerable to a remote panic via a malformed Data frame with SYN set and length 262145. No upstream release of libp2p-yamux eliminates the yamux 0.12.x dependency, so this commit introduces a local fork of libp2p-yamux under deps/libp2p-yamux that removes yamux012 entirely and uses yamux 0.13.10 exclusively. The xlayer-reth codebase only calls yamux::Config::default() which already routes through the yamux013 code path, so there is no functional change. Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 20 +-- Cargo.toml | 4 + deps/libp2p-yamux/Cargo.toml | 19 +++ deps/libp2p-yamux/src/lib.rs | 270 +++++++++++++++++++++++++++++++++++ 4 files changed, 294 insertions(+), 19 deletions(-) create mode 100644 deps/libp2p-yamux/Cargo.toml create mode 100644 deps/libp2p-yamux/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index eda15af8..4d8f3cb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5603,16 +5603,13 @@ dependencies = [ [[package]] name = "libp2p-yamux" version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8" dependencies = [ "either", "futures", "libp2p-core", "thiserror 2.0.18", "tracing", - "yamux 0.12.1", - "yamux 0.13.10", + "yamux", ] [[package]] @@ -14482,21 +14479,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0637d3a5566a82fa5214bae89087bc8c9fb94cd8e8a3c07feb691bb8d9c632db" -[[package]] -name = "yamux" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" -dependencies = [ - "futures", - "log", - "nohash-hasher", - "parking_lot", - "pin-project", - "rand 0.8.5", - "static_assertions", -] - [[package]] name = "yamux" version = "0.13.10" diff --git a/Cargo.toml b/Cargo.toml index f6e4c8ed..08b2759d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -276,3 +276,7 @@ op-alloy = { path = "deps/optimism/rust/op-alloy/crates/op-alloy" } # alloy-rpc-types-engine 1.6.3 requires jsonwebtoken ^9.3.0 (^9 semver), # which does not include 10.x. This local fork pins jsonwebtoken to 10.3.0. alloy-rpc-types-engine = { path = "deps/alloy-rpc-types-engine" } +# Security patch: remove yamux 0.12.x (CVE-2026-32314) from libp2p-yamux 0.47.0. +# libp2p-yamux 0.47.0 depends on both yamux 0.12.x (legacy) and 0.13.x (default). +# This local fork drops yamux012 and uses only yamux 0.13.x (already patched). +libp2p-yamux = { path = "deps/libp2p-yamux" } diff --git a/deps/libp2p-yamux/Cargo.toml b/deps/libp2p-yamux/Cargo.toml new file mode 100644 index 00000000..8c4f2ed1 --- /dev/null +++ b/deps/libp2p-yamux/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "libp2p-yamux" +edition = "2021" +rust-version = "1.73" +description = "Yamux multiplexing protocol for libp2p (yamux012 removed for CVE-2026-32314)" +version = "0.47.0" +authors = ["Parity Technologies "] +license = "MIT" +repository = "https://github.com/libp2p/rust-libp2p" +keywords = ["peer-to-peer", "libp2p", "networking"] +categories = ["network-programming", "asynchronous"] + +[dependencies] +either = "1" +futures = "0.3" +libp2p-core = "0.43" +thiserror = "2" +yamux = { version = "0.13.3", package = "yamux" } +tracing = "0.1" diff --git a/deps/libp2p-yamux/src/lib.rs b/deps/libp2p-yamux/src/lib.rs new file mode 100644 index 00000000..0c5536c7 --- /dev/null +++ b/deps/libp2p-yamux/src/lib.rs @@ -0,0 +1,270 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Implementation of the [Yamux](https://github.com/hashicorp/yamux/blob/master/spec.md) multiplexing protocol for libp2p. +//! +//! This is a security-patched fork of libp2p-yamux 0.47.0 that removes the +//! yamux 0.12.x dependency (CVE-2026-32314) and uses only yamux 0.13.x. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use std::{ + collections::VecDeque, + io, + io::{IoSlice, IoSliceMut}, + iter, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +use futures::{prelude::*, ready}; +use libp2p_core::{ + muxing::{StreamMuxer, StreamMuxerEvent}, + upgrade::{InboundConnectionUpgrade, OutboundConnectionUpgrade, UpgradeInfo}, +}; +use thiserror::Error; + +/// A Yamux connection. +#[derive(Debug)] +pub struct Muxer { + connection: yamux::Connection, + /// Temporarily buffers inbound streams in case our node is + /// performing backpressure on the remote. + inbound_stream_buffer: VecDeque, + /// Waker to be called when new inbound streams are available. + inbound_stream_waker: Option, +} + +/// How many streams to buffer before we start resetting them. +const MAX_BUFFERED_INBOUND_STREAMS: usize = 256; + +impl Muxer +where + C: AsyncRead + AsyncWrite + Send + Unpin + 'static, +{ + fn new(connection: yamux::Connection) -> Self { + Muxer { + connection, + inbound_stream_buffer: VecDeque::default(), + inbound_stream_waker: None, + } + } +} + +impl StreamMuxer for Muxer +where + C: AsyncRead + AsyncWrite + Unpin + 'static, +{ + type Substream = Stream; + type Error = Error; + + #[tracing::instrument(level = "trace", name = "StreamMuxer::poll_inbound", skip(self, cx))] + fn poll_inbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + if let Some(stream) = self.inbound_stream_buffer.pop_front() { + return Poll::Ready(Ok(stream)); + } + + if let Poll::Ready(res) = self.poll_inner(cx) { + return Poll::Ready(res); + } + + self.inbound_stream_waker = Some(cx.waker().clone()); + Poll::Pending + } + + #[tracing::instrument(level = "trace", name = "StreamMuxer::poll_outbound", skip(self, cx))] + fn poll_outbound( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let stream = ready!(self.connection.poll_new_outbound(cx)) + .map_err(Error) + .map(Stream)?; + Poll::Ready(Ok(stream)) + } + + #[tracing::instrument(level = "trace", name = "StreamMuxer::poll_close", skip(self, cx))] + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.connection.poll_close(cx).map_err(Error) + } + + #[tracing::instrument(level = "trace", name = "StreamMuxer::poll", skip(self, cx))] + fn poll( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + let this = self.get_mut(); + + let inbound_stream = ready!(this.poll_inner(cx))?; + + if this.inbound_stream_buffer.len() >= MAX_BUFFERED_INBOUND_STREAMS { + tracing::warn!( + stream=%inbound_stream.0, + "dropping stream because buffer is full" + ); + drop(inbound_stream); + } else { + this.inbound_stream_buffer.push_back(inbound_stream); + + if let Some(waker) = this.inbound_stream_waker.take() { + waker.wake() + } + } + + cx.waker().wake_by_ref(); + Poll::Pending + } +} + +/// A stream produced by the yamux multiplexer. +#[derive(Debug)] +pub struct Stream(yamux::Stream); + +impl AsyncRead for Stream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Pin::new(&mut self.0).poll_read(cx, buf) + } + + fn poll_read_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &mut [IoSliceMut<'_>], + ) -> Poll> { + Pin::new(&mut self.0).poll_read_vectored(cx, bufs) + } +} + +impl AsyncWrite for Stream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.0).poll_write(cx, buf) + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + Pin::new(&mut self.0).poll_write_vectored(cx, bufs) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_flush(cx) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.0).poll_close(cx) + } +} + +impl Muxer +where + C: AsyncRead + AsyncWrite + Unpin + 'static, +{ + fn poll_inner(&mut self, cx: &mut Context<'_>) -> Poll> { + let stream = ready!(self.connection.poll_next_inbound(cx)) + .ok_or(Error(yamux::ConnectionError::Closed))? + .map_err(Error) + .map(Stream)?; + + Poll::Ready(Ok(stream)) + } +} + +/// The yamux configuration. +#[derive(Debug, Clone)] +pub struct Config(yamux::Config); + +impl Default for Config { + fn default() -> Self { + let mut cfg = yamux::Config::default(); + cfg.set_read_after_close(false); + Self(cfg) + } +} + +impl Config { + /// Sets the maximum number of concurrent substreams. + pub fn set_max_num_streams(&mut self, num_streams: usize) -> &mut Self { + self.0.set_max_num_streams(num_streams); + self + } +} + +impl UpgradeInfo for Config { + type Info = &'static str; + type InfoIter = iter::Once; + + fn protocol_info(&self) -> Self::InfoIter { + iter::once("/yamux/1.0.0") + } +} + +impl InboundConnectionUpgrade for Config +where + C: AsyncRead + AsyncWrite + Send + Unpin + 'static, +{ + type Output = Muxer; + type Error = io::Error; + type Future = future::Ready>; + + fn upgrade_inbound(self, io: C, _: Self::Info) -> Self::Future { + let connection = yamux::Connection::new(io, self.0, yamux::Mode::Server); + future::ready(Ok(Muxer::new(connection))) + } +} + +impl OutboundConnectionUpgrade for Config +where + C: AsyncRead + AsyncWrite + Send + Unpin + 'static, +{ + type Output = Muxer; + type Error = io::Error; + type Future = future::Ready>; + + fn upgrade_outbound(self, io: C, _: Self::Info) -> Self::Future { + let connection = yamux::Connection::new(io, self.0, yamux::Mode::Client); + future::ready(Ok(Muxer::new(connection))) + } +} + +/// The Yamux [`StreamMuxer`] error type. +#[derive(Debug, Error)] +#[error(transparent)] +pub struct Error(yamux::ConnectionError); + +impl From for io::Error { + fn from(err: Error) -> Self { + match err.0 { + yamux::ConnectionError::Io(e) => e, + e => io::Error::new(io::ErrorKind::Other, e), + } + } +}