CVE-2023-53157
Description
The rosenpass crate before 0.2.1 for Rust allows remote attackers to cause a denial of service (panic) via a one-byte UDP packet.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Rosenpass crate before 0.2.1 for Rust panics on a single-byte UDP packet, allowing remote denial of service.
Vulnerability
Details The Rosenpass crate before version 0.2.1 for Rust fails to validate buffer sizes when decoding messages [1]. This oversight allows an attacker to trigger a panic by sending a UDP datagram with a single-byte payload [4]. The fix introduced envelope_truncating to handle undersized buffers gracefully [1].
Exploitation
An unauthenticated remote attacker can send a specially crafted one-byte UDP packet to any Rosenpass node listening on the network [3]. No prior authentication or special network position is required [4]. The packet causes the decoding routine to attempt operations on an undersized buffer, leading to a panicked state.
Impact
Successful exploitation results in a denial of service (DoS) as the Rosenpass process terminates unexpectedly [3]. This disrupts the post-quantum VPN key exchange, forcing reconnection and possible downtime.
Mitigation
The vulnerability is fixed in version 0.2.1 [1]. Users are advised to update immediately. There are no known workarounds for unpatched versions.
AI Insight generated on May 19, 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.
| Package | Affected versions | Patched versions |
|---|---|---|
rosenpasscrates.io | < 0.2.1 | 0.2.1 |
Affected products
2- rosenpass/rosenpassv5Range: 0
Patches
193439858d1c4fix crash on undersized buffers going through the lenses
3 files changed · +121 −28
rosenpass/src/lib.rs+1 −1 modified@@ -19,7 +19,7 @@ pub enum RosenpassError { Oqs, #[error("error from external library while calling OQS")] OqsExternalLib, - #[error("buffer size mismatch, required {required_size} but only found {actual_size}")] + #[error("buffer size mismatch, required {required_size} but found {actual_size}")] BufferSizeMismatch { required_size: usize, actual_size: usize,
rosenpass/src/msgs.rs+35 −5 modified@@ -143,7 +143,7 @@ macro_rules! data_lense( pub fn check_size(len: usize) -> Result<(), RosenpassError>{ let required_size = $( $len + )+ 0; let actual_size = len; - if required_size < actual_size { + if required_size != actual_size { Err(RosenpassError::BufferSizeMismatch { required_size, actual_size, @@ -199,23 +199,53 @@ macro_rules! data_lense( type __ContainerType; /// Create a lense to the byte slice - fn [< $type:snake >] $(< $($generic),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>; + fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>; + + /// Create a lense to the byte slice, automatically truncating oversized buffers + fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError>; } impl<'a> [< $type Ext >] for &'a [u8] { type __ContainerType = &'a [u8]; - fn [< $type:snake >] $(< $($generic),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> { + fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> { + $type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?; Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? )) } + + fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> { + let required_size = $( $len + )+ 0; + let actual_size = self.len(); + if actual_size < required_size { + return Err(RosenpassError::BufferSizeMismatch { + required_size, + actual_size, + }); + } + + [< $type Ext >]::[< $type:snake >](&self[..required_size]) + } } impl<'a> [< $type Ext >] for &'a mut [u8] { type __ContainerType = &'a mut [u8]; - - fn [< $type:snake >] $(< $($generic),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> { + fn [< $type:snake >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> { + $type::<Self::__ContainerType, $( $($generic),+ )? >::check_size(self.len())?; Ok($type ( self, $( $( ::core::marker::PhantomData::<$generic> ),+ )? )) } + + fn [< $type:snake _ truncating >] $(< $($generic : LenseView),* >)? (self) -> Result< $type<Self::__ContainerType, $( $($generic),+ )? >, RosenpassError> { + let required_size = $( $len + )+ 0; + let actual_size = self.len(); + if actual_size < required_size { + return Err(RosenpassError::BufferSizeMismatch { + required_size, + actual_size, + }); + } + + [< $type Ext >]::[< $type:snake >](&mut self[..required_size]) + } } }); );
rosenpass/src/protocol.rs+85 −22 modified@@ -736,7 +736,7 @@ impl CryptoServer { // TODO remove unnecessary copying between global tx_buf and per-peer buf // TODO move retransmission storage to io server pub fn initiate_handshake(&mut self, peer: PeerPtr, tx_buf: &mut [u8]) -> Result<usize> { - let mut msg = tx_buf.envelope::<InitHello<()>>()?; // Envelope::<InitHello>::default(); // TODO + let mut msg = tx_buf.envelope_truncating::<InitHello<()>>()?; // Envelope::<InitHello>::default(); // TODO self.handle_initiation(peer, msg.payload_mut().init_hello()?)?; let len = self.seal_and_commit_msg(peer, MsgType::InitHello, msg)?; peer.hs() @@ -793,7 +793,7 @@ impl CryptoServer { let msg_in = rx_buf.envelope::<InitHello<&[u8]>>()?; ensure!(msg_in.check_seal(self)?, seal_broken); - let mut msg_out = tx_buf.envelope::<RespHello<&mut [u8]>>()?; + let mut msg_out = tx_buf.envelope_truncating::<RespHello<&mut [u8]>>()?; let peer = self.handle_init_hello( msg_in.payload().init_hello()?, msg_out.payload_mut().resp_hello()?, @@ -805,7 +805,7 @@ impl CryptoServer { let msg_in = rx_buf.envelope::<RespHello<&[u8]>>()?; ensure!(msg_in.check_seal(self)?, seal_broken); - let mut msg_out = tx_buf.envelope::<InitConf<&mut [u8]>>()?; + let mut msg_out = tx_buf.envelope_truncating::<InitConf<&mut [u8]>>()?; let peer = self.handle_resp_hello( msg_in.payload().resp_hello()?, msg_out.payload_mut().init_conf()?, @@ -820,7 +820,7 @@ impl CryptoServer { let msg_in = rx_buf.envelope::<InitConf<&[u8]>>()?; ensure!(msg_in.check_seal(self)?, seal_broken); - let mut msg_out = tx_buf.envelope::<EmptyData<&mut [u8]>>()?; + let mut msg_out = tx_buf.envelope_truncating::<EmptyData<&mut [u8]>>()?; let peer = self.handle_init_conf( msg_in.payload().init_conf()?, msg_out.payload_mut().empty_data()?, @@ -1733,31 +1733,94 @@ impl CryptoServer { mod test { use super::*; - fn init_crypto_server() -> CryptoServer { - // always init libsodium before anything + #[test] + /// Ensure that the protocol implementation can deal with truncated + /// messages and with overlong messages. + /// + /// This test performs a complete handshake between two randomly generated + /// servers; instead of delivering the message correctly at first messages + /// of length zero through about 1.2 times the correct message size are delivered. + /// + /// Producing an error is expected on each of these messages. + /// + /// Finally the correct message is delivered and the same process + /// starts again in the other direction. + /// + /// Through all this, the handshake should still successfully terminate; + /// i.e. an exchanged key must be produced in both servers. + fn handles_incorrect_size_messages() { crate::sodium::sodium_init().unwrap(); - // initialize secret and public key for the crypto server - let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); - - // Guranteed to have 8MiB of stack size stacker::grow(8 * 1024 * 1024, || { - StaticKEM::keygen(sk.secret_mut(), pk.secret_mut()).expect("unable to generate keys"); + const OVERSIZED_MESSAGE: usize = ((MAX_MESSAGE_LEN as f32) * 1.2) as usize; + type MsgBufPlus = Public<OVERSIZED_MESSAGE>; + + const PEER0: PeerPtr = PeerPtr(0); + + let (mut me, mut they) = make_server_pair().unwrap(); + let (mut msgbuf, mut resbuf) = (MsgBufPlus::zero(), MsgBufPlus::zero()); + + // Process the entire handshake + let mut msglen = Some(me.initiate_handshake(PEER0, &mut *resbuf).unwrap()); + loop { + if let Some(l) = msglen { + std::mem::swap(&mut me, &mut they); + std::mem::swap(&mut msgbuf, &mut resbuf); + msglen = test_incorrect_sizes_for_msg(&mut me, &*msgbuf, l, &mut *resbuf); + } else { + break; + } + } + + assert_eq!( + me.osk(PEER0).unwrap().secret(), + they.osk(PEER0).unwrap().secret() + ); }); + } - CryptoServer::new(sk, pk) + /// Used in handles_incorrect_size_messages() to first deliver many truncated + /// and overlong messages, finally the correct message is delivered and the response + /// returned. + fn test_incorrect_sizes_for_msg( + srv: &mut CryptoServer, + msgbuf: &[u8], + msglen: usize, + resbuf: &mut [u8], + ) -> Option<usize> { + resbuf.fill(0); + + for l in 0..(((msglen as f32) * 1.2) as usize) { + if l == msglen { + continue; + } + + let res = srv.handle_msg(&msgbuf[..l], resbuf); + assert!(matches!(res, Err(_))); // handle_msg should raise an error + assert!(!resbuf.iter().find(|x| **x != 0).is_some()); // resbuf should not have been changed + } + + // Apply the proper handle_msg operation + srv.handle_msg(&msgbuf[..msglen], resbuf).unwrap().resp } - /// The determination of the message type relies on reading the first byte of the message. Only - /// after that the length of the message is checked against the specified message type. This - /// test ensures that nothing breaks in the case of an empty message. - #[test] - #[should_panic = "called `Result::unwrap()` on an `Err` value: received empty message, ignoring it"] - fn handle_empty_message() { - let mut crypt = init_crypto_server(); - let empty_rx_buf = [0u8; 0]; - let mut tx_buf = [0u8; 0]; + fn keygen() -> Result<(SSk, SPk)> { + // TODO: Copied from the benchmark; deduplicate + let (mut sk, mut pk) = (SSk::zero(), SPk::zero()); + StaticKEM::keygen(sk.secret_mut(), pk.secret_mut())?; + Ok((sk, pk)) + } - crypt.handle_msg(&empty_rx_buf, &mut tx_buf).unwrap(); + fn make_server_pair() -> Result<(CryptoServer, CryptoServer)> { + // TODO: Copied from the benchmark; deduplicate + let psk = SymKey::random(); + let ((ska, pka), (skb, pkb)) = (keygen()?, keygen()?); + let (mut a, mut b) = ( + CryptoServer::new(ska, pka.clone()), + CryptoServer::new(skb, pkb.clone()), + ); + a.add_peer(Some(psk.clone()), pkb)?; + b.add_peer(Some(psk), pka)?; + Ok((a, b)) } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.