Skip to content

Commit e2f611e

Browse files
committed
Truncate logged peer message strings
Counterparty-provided strings in network messages (Error, Warning, TxAbort) were logged without length limits, allowing a malicious peer to bloat log files. Some logging sites also lacked the same sanitization used for other untrusted strings. Add a `DebugMsg` struct and `log_msg!` macro that consistently truncate messages to 512 characters while preserving `PrintableString` sanitization. Replace all bare `msg.data` and ad hoc `PrintableString(&msg.data)` usages at the 7 relevant logging sites in `peer_handler.rs` and `channel.rs`. Co-Authored-By: HAL 9000
1 parent b81d85e commit e2f611e

3 files changed

Lines changed: 97 additions & 12 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,9 +2070,12 @@ where
20702070

20712071
let tx_abort = should_ack.then(|| {
20722072
let logger = WithChannelContext::from(logger, &self.context(), None);
2073-
let reason =
2074-
types::string::UntrustedString(String::from_utf8_lossy(&msg.data).to_string());
2075-
log_info!(logger, "Counterparty failed interactive transaction negotiation: {reason}");
2073+
let reason = String::from_utf8_lossy(&msg.data);
2074+
log_info!(
2075+
logger,
2076+
"Counterparty failed interactive transaction negotiation: {}",
2077+
log_msg!(reason)
2078+
);
20762079
msgs::TxAbort {
20772080
channel_id: msg.channel_id,
20782081
data: "Acknowledged tx_abort".to_string().into_bytes(),

lightning/src/ln/peer_handler.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ use crate::onion_message::packet::OnionMessageContents;
4545
use crate::routing::gossip::{NodeAlias, NodeId};
4646
use crate::sign::{NodeSigner, Recipient};
4747
use crate::types::features::{InitFeatures, NodeFeatures};
48-
use crate::types::string::PrintableString;
4948
use crate::util::atomic_counter::AtomicCounter;
5049
use crate::util::logger::{Level, Logger, WithContext};
5150
use crate::util::ser::{VecWriter, Writeable, Writer};
@@ -2384,15 +2383,15 @@ impl<
23842383
logger,
23852384
"Got Err message from {}: {}",
23862385
their_node_id,
2387-
PrintableString(&msg.data)
2386+
log_msg!(msg.data)
23882387
);
23892388
self.message_handler.chan_handler.handle_error(their_node_id, &msg);
23902389
if msg.channel_id.is_zero() {
23912390
return Err(PeerHandleError {}.into());
23922391
}
23932392
},
23942393
Message::Warning(msg) => {
2395-
log_debug!(logger, "Got warning message: {}", PrintableString(&msg.data));
2394+
log_debug!(logger, "Got warning message: {}", log_msg!(msg.data));
23962395
},
23972396

23982397
Message::Ping(msg) => {
@@ -3246,7 +3245,7 @@ impl<
32463245
msgs::ErrorAction::DisconnectPeer { msg } => {
32473246
if let Some(msg) = msg.as_ref() {
32483247
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler with message {}",
3249-
msg.data);
3248+
log_msg!(msg.data));
32503249
} else {
32513250
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler",
32523251
);
@@ -3260,7 +3259,7 @@ impl<
32603259
},
32613260
msgs::ErrorAction::DisconnectPeerWithWarning { msg } => {
32623261
log_trace!(logger, "Handling DisconnectPeer HandleError event in peer_handler with message {}",
3263-
msg.data);
3262+
log_msg!(msg.data));
32643263
// We do not have the peers write lock, so we just store that we're
32653264
// about to disconnect the peer and do it after we finish
32663265
// processing most messages.
@@ -3283,8 +3282,7 @@ impl<
32833282
},
32843283
msgs::ErrorAction::SendErrorMessage { msg } => {
32853284
log_trace!(logger, "Handling SendErrorMessage HandleError event in peer_handler with message {}",
3286-
3287-
msg.data);
3285+
log_msg!(msg.data));
32883286
let msg = Message::Error(msg);
32893287
self.enqueue_message(
32903288
&mut *get_peer_for_forwarding!(&node_id)?,
@@ -3293,8 +3291,7 @@ impl<
32933291
},
32943292
msgs::ErrorAction::SendWarningMessage { msg, ref log_level } => {
32953293
log_given_level!(logger, *log_level, "Handling SendWarningMessage HandleError event in peer_handler with message {}",
3296-
3297-
msg.data);
3294+
log_msg!(msg.data));
32983295
let msg = Message::Warning(msg);
32993296
self.enqueue_message(
33003297
&mut *get_peer_for_forwarding!(&node_id)?,

lightning/src/util/macro_logger.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,33 @@ macro_rules! log_spendable {
169169
};
170170
}
171171

172+
/// The maximum number of characters to display in a network message log entry.
173+
pub(crate) const LOG_MSG_MAX_LEN: usize = 512;
174+
175+
/// Wraps a string slice for Display, truncating to [`LOG_MSG_MAX_LEN`] characters and
176+
/// delegating sanitization to [`crate::types::string::PrintableString`].
177+
/// Useful for logging counterparty-provided messages.
178+
pub(crate) struct DebugMsg<'a>(pub &'a str);
179+
impl<'a> core::fmt::Display for DebugMsg<'a> {
180+
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
181+
let (msg, was_truncated) = match self.0.char_indices().nth(LOG_MSG_MAX_LEN) {
182+
Some((idx, _)) => (&self.0[..idx], true),
183+
None => (self.0, false),
184+
};
185+
core::fmt::Display::fmt(&crate::types::string::PrintableString(msg), f)?;
186+
if was_truncated {
187+
f.write_str("...")?;
188+
}
189+
Ok(())
190+
}
191+
}
192+
193+
macro_rules! log_msg {
194+
($obj: expr) => {
195+
$crate::util::macro_logger::DebugMsg(&$obj)
196+
};
197+
}
198+
172199
/// Create a new Record and log it. You probably don't want to use this macro directly,
173200
/// but it needs to be exported so `log_trace` etc can use it in external crates.
174201
#[doc(hidden)]
@@ -226,3 +253,61 @@ macro_rules! log_gossip {
226253
$crate::log_given_level!($logger, $crate::util::logger::Level::Gossip, $($arg)*);
227254
)
228255
}
256+
257+
#[cfg(test)]
258+
mod tests {
259+
use super::*;
260+
use alloc::string::ToString;
261+
262+
#[test]
263+
fn debug_msg_short_string() {
264+
let s = "hello world";
265+
assert_eq!(DebugMsg(s).to_string(), "hello world");
266+
}
267+
268+
#[test]
269+
fn debug_msg_truncates_at_limit() {
270+
let s: String = core::iter::repeat('a').take(LOG_MSG_MAX_LEN + 100).collect();
271+
let result = DebugMsg(&s).to_string();
272+
// Should be exactly LOG_MSG_MAX_LEN 'a's followed by "..."
273+
assert_eq!(result.len(), LOG_MSG_MAX_LEN + 3);
274+
assert!(result.ends_with("..."));
275+
}
276+
277+
#[test]
278+
fn debug_msg_no_truncation_at_exact_limit() {
279+
let s: String = core::iter::repeat('a').take(LOG_MSG_MAX_LEN).collect();
280+
let result = DebugMsg(&s).to_string();
281+
assert_eq!(result.len(), LOG_MSG_MAX_LEN);
282+
assert!(!result.ends_with("..."));
283+
}
284+
285+
#[test]
286+
fn debug_msg_replaces_control_characters() {
287+
let s = "hello\x00world\nfoo";
288+
let result = DebugMsg(s).to_string();
289+
assert_eq!(result, "hello\u{FFFD}world\u{FFFD}foo");
290+
}
291+
292+
#[test]
293+
fn debug_msg_uses_printable_string_sanitization() {
294+
let s = "safe\u{202E}cipsxe.exe";
295+
assert_eq!(DebugMsg(s).to_string(), crate::types::string::PrintableString(s).to_string());
296+
}
297+
298+
#[test]
299+
fn debug_msg_multibyte_unicode() {
300+
// Each emoji is multiple bytes but one character
301+
let s: String = core::iter::repeat('\u{1F600}').take(LOG_MSG_MAX_LEN + 10).collect();
302+
let result = DebugMsg(&s).to_string();
303+
let char_count: usize = result.chars().count();
304+
// LOG_MSG_MAX_LEN emoji chars + 3 chars for "..."
305+
assert_eq!(char_count, LOG_MSG_MAX_LEN + 3);
306+
assert!(result.ends_with("..."));
307+
}
308+
309+
#[test]
310+
fn debug_msg_empty_string() {
311+
assert_eq!(DebugMsg("").to_string(), "");
312+
}
313+
}

0 commit comments

Comments
 (0)