Don't create chan-closed mon update for outbound never-signed chans 2020-11-dup-chan-id-crash
authorMatt Corallo <git@bluematt.me>
Mon, 23 Nov 2020 18:34:31 +0000 (13:34 -0500)
committerMatt Corallo <git@bluematt.me>
Mon, 23 Nov 2020 22:00:07 +0000 (17:00 -0500)
Like the previous commit for channel-closed monitor updates for
inbound channels during processing of a funding_created message,
this resolves a more general issue for closing outbound channels
which have sent a funding_created but not yet received a
funding_signed.

This issue was also detected by full_stack_target.

To make similar issues easier to detect in testing and fuzzing, an
additional assertion is added to panic on updates to a channel
monitor before registering it.

lightning/src/chain/chainmonitor.rs
lightning/src/ln/channel.rs
lightning/src/ln/functional_tests.rs

index 240084af5733cf867649826f8a4cb4ee32a66372..13a0a883140994de21444992134492acedf0377f 100644 (file)
@@ -194,6 +194,13 @@ where C::Target: chain::Filter,
                match monitors.get_mut(&funding_txo) {
                        None => {
                                log_error!(self.logger, "Failed to update channel monitor: no such monitor registered");
+
+                               // We should never ever trigger this from within ChannelManager. Technically a
+                               // user could use this object with some proxying in between which makes this
+                               // possible, but in tests and fuzzing, this should be a panic.
+                               #[cfg(any(test, feature = "fuzztarget"))]
+                               panic!("ChannelManager generated a channel update for a channel that was not yet registered!");
+                               #[cfg(not(any(test, feature = "fuzztarget")))]
                                Err(ChannelMonitorUpdateErr::PermanentFailure)
                        },
                        Some(orig_monitor) => {
index c34fc6a38c28e5ae89a5cd98e094ee7a5348781a..abf2834c26a93d6b020ea095959014c08c963642 100644 (file)
@@ -4016,11 +4016,23 @@ impl<ChanSigner: ChannelKeys> Channel<ChanSigner> {
                                _ => {}
                        }
                }
+               let funding_txo = if let Some(funding_txo) = self.funding_txo {
+                       // If we haven't yet exchanged funding signatures (ie channel_state < FundingSent),
+                       // returning a channel monitor update here would imply a channel monitor update before
+                       // we even registered the channel monitor to begin with, which is invalid.
+                       // Thus, if we aren't actually at a point where we could conceivably broadcast the
+                       // funding transaction, don't return a funding txo (which prevents providing the
+                       // monitor update to the user, even if we return one).
+                       // See test_duplicate_chan_id and test_pre_lockin_no_chan_closed_update for more.
+                       if self.channel_state & (ChannelState::FundingSent as u32 | ChannelState::ChannelFunded as u32 | ChannelState::ShutdownComplete as u32) != 0 {
+                               Some(funding_txo.clone())
+                       } else { None }
+               } else { None };
 
                self.channel_state = ChannelState::ShutdownComplete as u32;
                self.update_time_counter += 1;
                self.latest_monitor_update_id += 1;
-               (self.funding_txo.clone(), ChannelMonitorUpdate {
+               (funding_txo, ChannelMonitorUpdate {
                        update_id: self.latest_monitor_update_id,
                        updates: vec![ChannelMonitorUpdateStep::ChannelForceClosed { should_broadcast }],
                }, dropped_outbound_htlcs)
index 3916a07decbdae74036da49b6e763bbbfd891a0a..85dae76987dc669424486e2a5d0f03387d469dcf 100644 (file)
@@ -8457,6 +8457,43 @@ fn test_concurrent_monitor_claim() {
        }
 }
 
+#[test]
+fn test_pre_lockin_no_chan_closed_update() {
+       // Test that if a peer closes a channel in response to a funding_created message we don't
+       // generate a channel update (as the channel cannot appear on chain without a funding_signed
+       // message).
+       //
+       // Doing so would imply a channel monitor update before the initial channel monitor
+       // registration, violating our API guarantees.
+       //
+       // Previously, full_stack_target managed to hit this case by opening then closing a channel,
+       // then opening a second channel with the same funding output as the first (which is not
+       // rejected because the first channel does not exist in the ChannelManager) and closing it
+       // before receiving funding_signed.
+       let chanmon_cfgs = create_chanmon_cfgs(2);
+       let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
+       let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
+       let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
+
+       // Create an initial channel
+       nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100000, 10001, 42, None).unwrap();
+       let mut open_chan_msg = get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id());
+       nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), InitFeatures::known(), &open_chan_msg);
+       let accept_chan_msg = get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id());
+       nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), InitFeatures::known(), &accept_chan_msg);
+
+       // Move the first channel through the funding flow...
+       let (temporary_channel_id, _tx, funding_output) = create_funding_transaction(&nodes[0], 100000, 42);
+
+       nodes[0].node.funding_transaction_generated(&temporary_channel_id, funding_output);
+       check_added_monitors!(nodes[0], 0);
+
+       let funding_created_msg = get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id());
+       let channel_id = ::chain::transaction::OutPoint { txid: funding_created_msg.funding_txid, index: funding_created_msg.funding_output_index }.to_channel_id();
+       nodes[0].node.handle_error(&nodes[1].node.get_our_node_id(), &msgs::ErrorMessage { channel_id, data: "Hi".to_owned() });
+       assert!(nodes[0].chain_monitor.added_monitors.lock().unwrap().is_empty());
+}
+
 #[test]
 fn test_htlc_no_detection() {
        // This test is a mutation to underscore the detection logic bug we had