Return `Err`s` instead of panicking on oversized messages
What changed, and why it matters
This commit changes how the Lightning networking code handles oversized encrypted messages. Previously, certain conditions would cause the program to crash with a panic. Now the code returns errors instead, which is a defensive improvement. However, one important call site still uses `.expect("TODO: Handled in the next commit")`, meaning the crash risk is not fully removed there yet. The commit is a partial patch toward making the node more resilient against denial-of-service from malformed or oversized peer traffic.
Treat as a defensive hardening commit. Verify the follow-up commit that removes the remaining `.expect("TODO: Handled in the next commit")` in `peer_handler.rs::enqueue_message` and properly propagates the error to `PeerManager`. Until that is merged, the node can still crash if an outbound message exceeds the maximum length during serialization.
Security signals we found
panic-to-error conversion for oversized message encryption/decryption
denial-of-service hardening against oversized peer messages
debug_assert retained to preserve test coverage of invariant violations
one remaining .expect in enqueue_message indicates incomplete remediation
gossip broadcast paths now silently drop oversized messages
Evidence from the diff
The patch converts panics to Result/Err returns in PeerChannelEncryptor and MessageBuf for oversized messages and incomplete Noise handshakes. encrypt_message_with_header_0s, encrypt_message, MessageBuf::from_encoded, and decrypt_message now return errors. Callers in the fuzz harness and gossip broadcast paths handle the new Result. The peer_handler.rs enqueue_message path still unwraps with .expect("TODO: Handled in the next commit"), leaving a residual panic surface. The commit message explicitly notes this is a step toward later handling in PeerManager.
Changed components
lightning/src/ln/peer_channel_encryptor.rslightning/src/ln/peer_handler.rsfuzz/src/peer_crypt.rsInspect captured patch +68 / −31
diff --git a/fuzz/src/peer_crypt.rs b/fuzz/src/peer_crypt.rs
index b01aa02..afd6cce 100644
--- a/fuzz/src/peer_crypt.rs
+++ b/fuzz/src/peer_crypt.rs
@@ -79,9 +79,8 @@ pub fn do_test(data: &[u8]) {
let mut buf = [0; 65536 + 16];
loop {
if get_slice!(1)[0] == 0 {
- crypter.encrypt_buffer(MessageBuf::from_encoded(&get_slice!(slice_to_be16(
- get_slice!(2)
- ))));
+ let msg = MessageBuf::from_encoded(&get_slice!(slice_to_be16(get_slice!(2)))).unwrap();
+ crypter.encrypt_buffer(msg);
} else {
let len = match crypter.decrypt_length_header(get_slice!(16 + 2)) {
Ok(len) => len,
diff --git a/lightning/src/ln/peer_channel_encryptor.rs b/lightning/src/ln/peer_channel_encryptor.rs
index 5f46131..7c0917d 100644
--- a/lightning/src/ln/peer_channel_encryptor.rs
+++ b/lightning/src/ln/peer_channel_encryptor.rs
@@ -10,7 +10,7 @@
use crate::prelude::*;
use crate::ln::msgs;
-use crate::ln::msgs::LightningError;
+use crate::ln::msgs::{ErrorAction, LightningError};
use crate::ln::wire;
use crate::ln::wire::Type;
use crate::sign::{NodeSigner, Recipient};
@@ -515,10 +515,11 @@ impl PeerChannelEncryptor {
///
/// For effeciency, the [`Vec::capacity`] should be at least 16 bytes larger than the
/// [`Vec::len`], to avoid reallocating for the message MAC, which will be appended to the vec.
- fn encrypt_message_with_header_0s(&mut self, msgbuf: &mut Vec<u8>) {
+ fn encrypt_message_with_header_0s(&mut self, msgbuf: &mut Vec<u8>) -> Result<(), ()> {
let msg_len = msgbuf.len() - 16 - 2;
if msg_len > LN_MAX_MSG_LEN {
- panic!("Attempted to encrypt message longer than 65535 bytes!");
+ debug_assert!(false, "Attempted to encrypt message longer than 65535 bytes!");
+ return Err(());
}
match self.noise_state {
@@ -541,22 +542,32 @@ impl PeerChannelEncryptor {
Self::encrypt_in_place_with_ad(msgbuf, 16 + 2, *sn, sk, &[0; 0]);
*sn += 1;
+ Ok(())
+ },
+ _ => {
+ debug_assert!(
+ false,
+ "Tried to encrypt a message prior to noise handshake completion"
+ );
+ Err(())
},
- _ => panic!("Tried to encrypt a message prior to noise handshake completion"),
}
}
/// Encrypts the given pre-serialized message, returning the encrypted version.
- /// panics if msg.len() > 65535 or Noise handshake has not finished.
pub fn encrypt_buffer(&mut self, mut msg: MessageBuf) -> Vec<u8> {
- self.encrypt_message_with_header_0s(&mut msg.0);
+ self.encrypt_message_with_header_0s(&mut msg.0)
+ .expect("Length was checked in buf constructor and peer should be live");
msg.0
}
/// Encrypts the given message, returning the encrypted version.
- /// panics if the length of `message`, once encoded, is greater than 65535 or if the Noise
- /// handshake has not finished.
- pub(crate) fn encrypt_message<T: wire::Type>(&mut self, message: wire::Message<T>) -> Vec<u8> {
+ ///
+ /// Returns `Err(())` if the length of `message`, once encoded, is greater than 65535 or if the
+ /// Noise handshake has not finished.
+ pub(crate) fn encrypt_message<T: wire::Type>(
+ &mut self, message: wire::Message<T>,
+ ) -> Result<Vec<u8>, ()> {
// Allocate a buffer with 2KB, fitting most common messages. Reserve the first 16+2 bytes
// for the 2-byte message type prefix and its MAC.
let mut res = VecWriter(Vec::with_capacity(MSG_BUF_ALLOC_SIZE));
@@ -565,8 +576,8 @@ impl PeerChannelEncryptor {
message.type_id().write(&mut res).expect("In-memory messages must never fail to serialize");
message.write(&mut res).expect("In-memory messages must never fail to serialize");
- self.encrypt_message_with_header_0s(&mut res.0);
- res.0
+ self.encrypt_message_with_header_0s(&mut res.0)?;
+ Ok(res.0)
}
/// Decrypts a message length header from the remote peer.
@@ -595,10 +606,14 @@ impl PeerChannelEncryptor {
/// Decrypts the given message up to msg.len() - 16. Bytes after msg.len() - 16 will be left
/// undefined (as they contain the Poly1305 tag bytes).
///
- /// panics if msg.len() > 65535 + 16
+ /// Returns an error if `msg.len() > 65535 + 16`.
pub fn decrypt_message(&mut self, msg: &mut [u8]) -> Result<(), LightningError> {
if msg.len() > LN_MAX_MSG_LEN + 16 {
- panic!("Attempted to decrypt message longer than 65535 + 16 bytes!");
+ debug_assert!(false, "Attempted to decrypt message longer than 65535 + 16 bytes!");
+ return Err(LightningError {
+ err: "Somehow had an oversized message to decrypt".to_owned(),
+ action: ErrorAction::DisconnectPeer { msg: None },
+ });
}
match self.noise_state {
@@ -642,17 +657,18 @@ impl MessageBuf {
/// Creates a new buffer from an encoded message (i.e. the two message-type bytes followed by
/// the message contents).
///
- /// Panics if the message is longer than 2^16.
- pub fn from_encoded(encoded_msg: &[u8]) -> Self {
+ /// Returns `Err(())` if the message is longer than 2^16 - 1.
+ pub fn from_encoded(encoded_msg: &[u8]) -> Result<Self, ()> {
if encoded_msg.len() > LN_MAX_MSG_LEN {
- panic!("Attempted to encrypt message longer than 65535 bytes!");
+ debug_assert!(false, "Attempted to encrypt message longer than 65535 bytes!");
+ return Err(());
}
// In addition to the message (continaing the two message type bytes), we also have to add
// the message length header (and its MAC) and the message MAC.
let mut res = Vec::with_capacity(encoded_msg.len() + 16 * 2 + 2);
res.resize(encoded_msg.len() + 16 + 2, 0);
res[16 + 2..].copy_from_slice(&encoded_msg);
- Self(res)
+ Ok(Self(res))
}
#[cfg(test)]
@@ -1008,7 +1024,8 @@ mod tests {
for i in 0..1005 {
let msg = [0x68, 0x65, 0x6c, 0x6c, 0x6f];
- let mut res = outbound_peer.encrypt_buffer(MessageBuf::from_encoded(&msg));
+ let msgbuf = MessageBuf::from_encoded(&msg).unwrap();
+ let mut res = outbound_peer.encrypt_buffer(msgbuf);
assert_eq!(res.len(), 5 + 2 * 16 + 2);
let len_header = res[0..2 + 16].to_vec();
@@ -1049,20 +1066,38 @@ mod tests {
}
#[test]
+ #[cfg(debug_assertions)]
#[should_panic(expected = "Attempted to encrypt message longer than 65535 bytes!")]
fn max_message_len_encryption() {
- let mut outbound_peer = get_outbound_peer_for_initiator_test_vectors();
let msg = [4u8; LN_MAX_MSG_LEN + 1];
- outbound_peer.encrypt_buffer(MessageBuf::from_encoded(&msg));
+ let _ = MessageBuf::from_encoded(&msg);
+ }
+
+ #[test]
+ #[cfg(not(debug_assertions))]
+ fn max_message_len_encryption() {
+ let msg = [4u8; LN_MAX_MSG_LEN + 1];
+ assert!(MessageBuf::from_encoded(&msg).is_err());
}
#[test]
+ #[cfg(debug_assertions)]
#[should_panic(expected = "Attempted to decrypt message longer than 65535 + 16 bytes!")]
fn max_message_len_decryption() {
let mut inbound_peer = get_inbound_peer_for_test_vectors();
// MSG should not exceed LN_MAX_MSG_LEN + 16
let mut msg = [4u8; LN_MAX_MSG_LEN + 17];
- inbound_peer.decrypt_message(&mut msg).unwrap();
+ let _ = inbound_peer.decrypt_message(&mut msg);
+ }
+
+ #[test]
+ #[cfg(not(debug_assertions))]
+ fn max_message_len_decryption() {
+ let mut inbound_peer = get_inbound_peer_for_test_vectors();
+
+ // MSG should not exceed LN_MAX_MSG_LEN + 16
+ let mut msg = [4u8; LN_MAX_MSG_LEN + 17];
+ assert!(inbound_peer.decrypt_message(&mut msg).is_err());
}
}
diff --git a/lightning/src/ln/peer_handler.rs b/lightning/src/ln/peer_handler.rs
index 8a983c6..ac6a812 100644
--- a/lightning/src/ln/peer_handler.rs
+++ b/lightning/src/ln/peer_handler.rs
@@ -1716,7 +1716,7 @@ impl<
debug_assert!(false, "node_id should be set by the time we send a message");
}
peer.msgs_sent_since_pong += 1;
- peer.pending_outbound_buffer.push_back(peer.channel_encryptor.encrypt_message(message));
+ peer.pending_outbound_buffer.push_back(peer.channel_encryptor.encrypt_message(message).expect("TODO: Handled in the next commit"));
}
fn do_read_event(
@@ -2661,8 +2661,9 @@ impl<
{
continue;
}
- let encoded_message = MessageBuf::from_encoded(&encoded_msg);
- peer.gossip_broadcast_buffer.push_back(encoded_message);
+ if let Ok(encoded_message) = MessageBuf::from_encoded(&encoded_msg) {
+ peer.gossip_broadcast_buffer.push_back(encoded_message);
+ }
}
},
BroadcastGossipMessage::NodeAnnouncement(msg) => {
@@ -2707,8 +2708,9 @@ impl<
{
continue;
}
- let encoded_message = MessageBuf::from_encoded(&encoded_msg);
- peer.gossip_broadcast_buffer.push_back(encoded_message);
+ if let Ok(encoded_message) = MessageBuf::from_encoded(&encoded_msg) {
+ peer.gossip_broadcast_buffer.push_back(encoded_message);
+ }
}
},
BroadcastGossipMessage::ChannelUpdate { msg, node_id_1, node_id_2 } => {
@@ -2747,8 +2749,9 @@ impl<
{
continue;
}
- let encoded_message = MessageBuf::from_encoded(&encoded_msg);
- peer.gossip_broadcast_buffer.push_back(encoded_message);
+ if let Ok(encoded_message) = MessageBuf::from_encoded(&encoded_msg) {
+ peer.gossip_broadcast_buffer.push_back(encoded_message);
+ }
}
},
}
Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.