Skip to content

Commit 987ab95

Browse files
committed
SanitizedString struct to safely Display CounterpartyForceClosed peer_msg
1 parent 217c3e0 commit 987ab95

8 files changed

+49
-14
lines changed

lightning/src/ln/channelmanager.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ use crate::util::events::{Event, EventHandler, EventsProvider, MessageSendEvent,
5959
use crate::util::events;
6060
use crate::util::wakers::{Future, Notifier};
6161
use crate::util::scid_utils::fake_scid;
62+
use crate::util::string::UntrustedString;
6263
use crate::util::ser::{BigSize, FixedLengthReader, Readable, ReadableArgs, MaybeReadable, Writeable, Writer, VecWriter};
6364
use crate::util::logger::{Level, Logger};
6465
use crate::util::errors::APIError;
@@ -1990,7 +1991,7 @@ where
19901991
let peer_state = &mut *peer_state_lock;
19911992
if let hash_map::Entry::Occupied(chan) = peer_state.channel_by_id.entry(channel_id.clone()) {
19921993
if let Some(peer_msg) = peer_msg {
1993-
self.issue_channel_close_events(chan.get(),ClosureReason::CounterpartyForceClosed { peer_msg: peer_msg.to_string() });
1994+
self.issue_channel_close_events(chan.get(),ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(peer_msg.to_string()) });
19941995
} else {
19951996
self.issue_channel_close_events(chan.get(),ClosureReason::HolderForceClosed);
19961997
}

lightning/src/ln/functional_tests.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use crate::util::test_utils;
3434
use crate::util::events::{Event, MessageSendEvent, MessageSendEventsProvider, PathFailure, PaymentPurpose, ClosureReason, HTLCDestination};
3535
use crate::util::errors::APIError;
3636
use crate::util::ser::{Writeable, ReadableArgs};
37+
use crate::util::string::UntrustedString;
3738
use crate::util::config::UserConfig;
3839

3940
use bitcoin::hash_types::BlockHash;
@@ -8344,7 +8345,7 @@ fn test_pre_lockin_no_chan_closed_update() {
83448345
let channel_id = crate::chain::transaction::OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }.to_channel_id();
83458346
nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id, data: "Hi".to_owned() });
83468347
assert!(nodes[0].chain_monitor.added_monitors.lock().unwrap().is_empty());
8347-
check_closed_event!(nodes[0], 2, ClosureReason::CounterpartyForceClosed { peer_msg: "Hi".to_string() }, true);
8348+
check_closed_event!(nodes[0], 2, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("Hi".to_string()) }, true);
83488349
}
83498350

83508351
#[test]
@@ -8802,7 +8803,7 @@ fn test_error_chans_closed() {
88028803
nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id: chan_2.2, data: "ERR".to_owned() });
88038804
check_added_monitors!(nodes[0], 1);
88048805
check_closed_broadcast!(nodes[0], false);
8805-
check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyForceClosed { peer_msg: "ERR".to_string() });
8806+
check_closed_event!(nodes[0], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) });
88068807
assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0).len(), 1);
88078808
assert_eq!(nodes[0].node.list_usable_channels().len(), 2);
88088809
assert!(nodes[0].node.list_usable_channels()[0].channel_id == chan_1.2 || nodes[0].node.list_usable_channels()[1].channel_id == chan_1.2);
@@ -8812,7 +8813,7 @@ fn test_error_chans_closed() {
88128813
let _chan_4 = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001);
88138814
nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id: [0; 32], data: "ERR".to_owned() });
88148815
check_added_monitors!(nodes[0], 2);
8815-
check_closed_event!(nodes[0], 2, ClosureReason::CounterpartyForceClosed { peer_msg: "ERR".to_string() });
8816+
check_closed_event!(nodes[0], 2, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString("ERR".to_string()) });
88168817
let events = nodes[0].node.get_and_clear_pending_msg_events();
88178818
assert_eq!(events.len(), 2);
88188819
match events[0] {

lightning/src/ln/payment_tests.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use crate::util::events::{ClosureReason, Event, HTLCDestination, MessageSendEven
2828
use crate::util::test_utils;
2929
use crate::util::errors::APIError;
3030
use crate::util::ser::Writeable;
31+
use crate::util::string::UntrustedString;
3132

3233
use bitcoin::{Block, BlockHeader, TxMerkleNode};
3334
use bitcoin::hashes::Hash;
@@ -353,7 +354,7 @@ fn do_retry_with_no_persist(confirm_before_reload: bool) {
353354
MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
354355
assert_eq!(node_id, nodes[1].node.get_our_node_id());
355356
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), msg);
356-
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id()) });
357+
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id())) });
357358
check_added_monitors!(nodes[1], 1);
358359
assert_eq!(nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0).len(), 1);
359360
},
@@ -518,7 +519,7 @@ fn do_test_completed_payment_not_retryable_on_reload(use_dust: bool) {
518519
MessageSendEvent::HandleError { node_id, action: msgs::ErrorAction::SendErrorMessage { ref msg } } => {
519520
assert_eq!(node_id, nodes[1].node.get_our_node_id());
520521
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), msg);
521-
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id()) });
522+
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id())) });
522523
check_added_monitors!(nodes[1], 1);
523524
bs_commitment_tx = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
524525
},

lightning/src/ln/reload_tests.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::util::errors::APIError;
2323
use crate::util::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider};
2424
use crate::util::ser::{Writeable, ReadableArgs};
2525
use crate::util::config::UserConfig;
26+
use crate::util::string::UntrustedString;
2627

2728
use bitcoin::hash_types::BlockHash;
2829

@@ -566,7 +567,7 @@ fn do_test_data_loss_protect(reconnect_panicing: bool) {
566567
nodes[1].node.handle_error(&nodes[0].node.get_our_node_id(), &err_msgs_0[0]);
567568
assert!(nodes[1].node.list_usable_channels().is_empty());
568569
check_added_monitors!(nodes[1], 1);
569-
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id()) });
570+
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id())) });
570571
check_closed_broadcast!(nodes[1], false);
571572
}
572573

lightning/src/ln/reorg_tests.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::ln::msgs::{ChannelMessageHandler, Init};
1717
use crate::util::events::{Event, MessageSendEventsProvider, ClosureReason, HTLCDestination};
1818
use crate::util::test_utils;
1919
use crate::util::ser::Writeable;
20+
use crate::util::string::UntrustedString;
2021

2122
use bitcoin::blockdata::block::{Block, BlockHeader};
2223
use bitcoin::blockdata::script::Builder;
@@ -368,7 +369,7 @@ fn do_test_unconf_chan(reload_node: bool, reorg_after_reload: bool, use_funding_
368369
nodes[0].node.test_process_background_events(); // Required to free the pending background monitor update
369370
check_added_monitors!(nodes[0], 1);
370371
let expected_err = "Funding transaction was un-confirmed. Locked at 6 confs, now have 0 confs.";
371-
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: "Channel closed because of an exception: ".to_owned() + expected_err });
372+
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(format!("Channel closed because of an exception: {}", expected_err)) });
372373
check_closed_event!(nodes[0], 1, ClosureReason::ProcessingError { err: expected_err.to_owned() });
373374
assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1);
374375
nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clear();

lightning/src/ln/shutdown_tests.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::util::test_utils::OnGetShutdownScriptpubkey;
2121
use crate::util::events::{Event, MessageSendEvent, MessageSendEventsProvider, ClosureReason};
2222
use crate::util::errors::APIError;
2323
use crate::util::config::UserConfig;
24+
use crate::util::string::UntrustedString;
2425

2526
use bitcoin::blockdata::script::Builder;
2627
use bitcoin::blockdata::opcodes;
@@ -380,7 +381,7 @@ fn do_test_shutdown_rebroadcast(recv_count: u8) {
380381
// closing_signed so we do it ourselves
381382
check_closed_broadcast!(nodes[1], false);
382383
check_added_monitors!(nodes[1], 1);
383-
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id()) });
384+
check_closed_event!(nodes[1], 1, ClosureReason::CounterpartyForceClosed { peer_msg: UntrustedString(format!("Got a message for a channel from the wrong node! No such channel for the passed counterparty_node_id {}", &nodes[1].node.get_our_node_id())) });
384385
}
385386

386387
assert!(nodes[0].node.list_channels().is_empty());

lightning/src/util/events.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::ln::{PaymentPreimage, PaymentHash, PaymentSecret};
2525
use crate::routing::gossip::NetworkUpdate;
2626
use crate::util::errors::APIError;
2727
use crate::util::ser::{BigSize, FixedLengthReader, Writeable, Writer, MaybeReadable, Readable, RequiredWrapper, UpgradableRequired, WithoutLength};
28+
use crate::util::string::UntrustedString;
2829
use crate::routing::router::{RouteHop, RouteParameters};
2930

3031
use bitcoin::{PackedLockTime, Transaction};
@@ -125,10 +126,12 @@ pub enum ClosureReason {
125126
CounterpartyForceClosed {
126127
/// The error which the peer sent us.
127128
///
128-
/// The string should be sanitized before it is used (e.g emitted to logs
129-
/// or printed to stdout). Otherwise, a well crafted error message may exploit
129+
/// Be careful about printing the peer_msg, a well-crafted message could exploit
130130
/// a security vulnerability in the terminal emulator or the logging subsystem.
131-
peer_msg: String,
131+
/// To be safe, use `Display` on `UntrustedString`
132+
///
133+
/// [`UntrustedString`]: crate::util::string::UntrustedString
134+
peer_msg: UntrustedString,
132135
},
133136
/// Closure generated from [`ChannelManager::force_close_channel`], called by the user.
134137
///
@@ -173,8 +176,7 @@ impl core::fmt::Display for ClosureReason {
173176
f.write_str("Channel closed because ")?;
174177
match self {
175178
ClosureReason::CounterpartyForceClosed { peer_msg } => {
176-
f.write_str("counterparty force-closed with message ")?;
177-
f.write_str(&peer_msg)
179+
f.write_fmt(format_args!("counterparty force-closed with message: {}", peer_msg))
178180
},
179181
ClosureReason::HolderForceClosed => f.write_str("user manually force-closed the channel"),
180182
ClosureReason::CooperativeClosure => f.write_str("the channel was cooperatively closed"),

lightning/src/util/string.rs

+27
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,34 @@
99

1010
//! Utilities for strings.
1111
12+
use alloc::string::String;
1213
use core::fmt;
14+
use crate::io::{self, Read};
15+
use crate::ln::msgs;
16+
use crate::util::ser::{Writeable, Writer, Readable};
17+
18+
/// Struct to `Display` fields in a safe way using `PrintableString`
19+
#[derive(Clone, Debug, PartialEq, Eq)]
20+
pub struct UntrustedString(pub String);
21+
22+
impl Writeable for UntrustedString {
23+
fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
24+
self.0.write(w)
25+
}
26+
}
27+
28+
impl Readable for UntrustedString {
29+
fn read<R: Read>(r: &mut R) -> Result<Self, msgs::DecodeError> {
30+
let s: String = Readable::read(r)?;
31+
Ok(Self(s))
32+
}
33+
}
34+
35+
impl fmt::Display for UntrustedString {
36+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37+
PrintableString(&self.0).fmt(f)
38+
}
39+
}
1340

1441
/// A string that displays only printable characters, replacing control characters with
1542
/// [`core::char::REPLACEMENT_CHARACTER`].

0 commit comments

Comments
 (0)