Skip to content

Commit 36570f4

Browse files
authored
Merge pull request #890 from TheBlueMatt/2021-04-fix-chan-shutdown-crash
Fix (and test) panic when our counterparty uses a bogus funding tx
2 parents 0d75a63 + eb42caf commit 36570f4

File tree

3 files changed

+117
-53
lines changed

3 files changed

+117
-53
lines changed

lightning/src/ln/channel.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3574,7 +3574,6 @@ impl<Signer: Sign> Channel<Signer> {
35743574
#[cfg(not(feature = "fuzztarget"))]
35753575
panic!("Client called ChannelManager::funding_transaction_generated with bogus transaction!");
35763576
}
3577-
self.channel_state = ChannelState::ShutdownComplete as u32;
35783577
self.update_time_counter += 1;
35793578
return Err(msgs::ErrorMessage {
35803579
channel_id: self.channel_id(),

lightning/src/ln/channelmanager.rs

Lines changed: 67 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,61 +1564,14 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
15641564
}
15651565
}
15661566

1567-
/// Call this upon creation of a funding transaction for the given channel.
1568-
///
1569-
/// Returns an [`APIError::APIMisuseError`] if the funding_transaction spent non-SegWit outputs
1570-
/// or if no output was found which matches the parameters in [`Event::FundingGenerationReady`].
1571-
///
1572-
/// Panics if a funding transaction has already been provided for this channel.
1573-
///
1574-
/// May panic if the output found in the funding transaction is duplicative with some other
1575-
/// channel (note that this should be trivially prevented by using unique funding transaction
1576-
/// keys per-channel).
1577-
///
1578-
/// Do NOT broadcast the funding transaction yourself. When we have safely received our
1579-
/// counterparty's signature the funding transaction will automatically be broadcast via the
1580-
/// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed.
1581-
///
1582-
/// Note that this includes RBF or similar transaction replacement strategies - lightning does
1583-
/// not currently support replacing a funding transaction on an existing channel. Instead,
1584-
/// create a new channel with a conflicting funding transaction.
1585-
pub fn funding_transaction_generated(&self, temporary_channel_id: &[u8; 32], funding_transaction: Transaction) -> Result<(), APIError> {
1586-
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
1587-
1588-
for inp in funding_transaction.input.iter() {
1589-
if inp.witness.is_empty() {
1590-
return Err(APIError::APIMisuseError {
1591-
err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
1592-
});
1593-
}
1594-
}
1595-
1567+
/// Handles the generation of a funding transaction, optionally (for tests) with a function
1568+
/// which checks the correctness of the funding transaction given the associated channel.
1569+
fn funding_transaction_generated_intern<FundingOutput: Fn(&Channel<Signer>, &Transaction) -> Result<OutPoint, APIError>>
1570+
(&self, temporary_channel_id: &[u8; 32], funding_transaction: Transaction, find_funding_output: FundingOutput) -> Result<(), APIError> {
15961571
let (chan, msg) = {
15971572
let (res, chan) = match self.channel_state.lock().unwrap().by_id.remove(temporary_channel_id) {
15981573
Some(mut chan) => {
1599-
let mut output_index = None;
1600-
let expected_spk = chan.get_funding_redeemscript().to_v0_p2wsh();
1601-
for (idx, outp) in funding_transaction.output.iter().enumerate() {
1602-
if outp.script_pubkey == expected_spk && outp.value == chan.get_value_satoshis() {
1603-
if output_index.is_some() {
1604-
return Err(APIError::APIMisuseError {
1605-
err: "Multiple outputs matched the expected script and value".to_owned()
1606-
});
1607-
}
1608-
if idx > u16::max_value() as usize {
1609-
return Err(APIError::APIMisuseError {
1610-
err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
1611-
});
1612-
}
1613-
output_index = Some(idx as u16);
1614-
}
1615-
}
1616-
if output_index.is_none() {
1617-
return Err(APIError::APIMisuseError {
1618-
err: "No output matched the script_pubkey and value in the FundingGenerationReady event".to_owned()
1619-
});
1620-
}
1621-
let funding_txo = OutPoint { txid: funding_transaction.txid(), index: output_index.unwrap() };
1574+
let funding_txo = find_funding_output(&chan, &funding_transaction)?;
16221575

16231576
(chan.get_outbound_funding_created(funding_transaction, funding_txo, &self.logger)
16241577
.map_err(|e| if let ChannelError::Close(msg) = e {
@@ -1654,6 +1607,68 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
16541607
Ok(())
16551608
}
16561609

1610+
#[cfg(test)]
1611+
pub(crate) fn funding_transaction_generated_unchecked(&self, temporary_channel_id: &[u8; 32], funding_transaction: Transaction, output_index: u16) -> Result<(), APIError> {
1612+
self.funding_transaction_generated_intern(temporary_channel_id, funding_transaction, |_, tx| {
1613+
Ok(OutPoint { txid: tx.txid(), index: output_index })
1614+
})
1615+
}
1616+
1617+
/// Call this upon creation of a funding transaction for the given channel.
1618+
///
1619+
/// Returns an [`APIError::APIMisuseError`] if the funding_transaction spent non-SegWit outputs
1620+
/// or if no output was found which matches the parameters in [`Event::FundingGenerationReady`].
1621+
///
1622+
/// Panics if a funding transaction has already been provided for this channel.
1623+
///
1624+
/// May panic if the output found in the funding transaction is duplicative with some other
1625+
/// channel (note that this should be trivially prevented by using unique funding transaction
1626+
/// keys per-channel).
1627+
///
1628+
/// Do NOT broadcast the funding transaction yourself. When we have safely received our
1629+
/// counterparty's signature the funding transaction will automatically be broadcast via the
1630+
/// [`BroadcasterInterface`] provided when this `ChannelManager` was constructed.
1631+
///
1632+
/// Note that this includes RBF or similar transaction replacement strategies - lightning does
1633+
/// not currently support replacing a funding transaction on an existing channel. Instead,
1634+
/// create a new channel with a conflicting funding transaction.
1635+
pub fn funding_transaction_generated(&self, temporary_channel_id: &[u8; 32], funding_transaction: Transaction) -> Result<(), APIError> {
1636+
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier);
1637+
1638+
for inp in funding_transaction.input.iter() {
1639+
if inp.witness.is_empty() {
1640+
return Err(APIError::APIMisuseError {
1641+
err: "Funding transaction must be fully signed and spend Segwit outputs".to_owned()
1642+
});
1643+
}
1644+
}
1645+
self.funding_transaction_generated_intern(temporary_channel_id, funding_transaction, |chan, tx| {
1646+
let mut output_index = None;
1647+
let expected_spk = chan.get_funding_redeemscript().to_v0_p2wsh();
1648+
for (idx, outp) in tx.output.iter().enumerate() {
1649+
if outp.script_pubkey == expected_spk && outp.value == chan.get_value_satoshis() {
1650+
if output_index.is_some() {
1651+
return Err(APIError::APIMisuseError {
1652+
err: "Multiple outputs matched the expected script and value".to_owned()
1653+
});
1654+
}
1655+
if idx > u16::max_value() as usize {
1656+
return Err(APIError::APIMisuseError {
1657+
err: "Transaction had more than 2^16 outputs, which is not supported".to_owned()
1658+
});
1659+
}
1660+
output_index = Some(idx as u16);
1661+
}
1662+
}
1663+
if output_index.is_none() {
1664+
return Err(APIError::APIMisuseError {
1665+
err: "No output matched the script_pubkey and value in the FundingGenerationReady event".to_owned()
1666+
});
1667+
}
1668+
Ok(OutPoint { txid: tx.txid(), index: output_index.unwrap() })
1669+
})
1670+
}
1671+
16571672
fn get_announcement_sigs(&self, chan: &Channel<Signer>) -> Option<msgs::AnnouncementSignatures> {
16581673
if !chan.should_announce() {
16591674
log_trace!(self.logger, "Can't send announcement_signatures for private channel {}", log_bytes!(chan.channel_id()));

lightning/src/ln/functional_tests.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8828,3 +8828,53 @@ fn test_error_chans_closed() {
88288828
assert_eq!(nodes[0].node.list_usable_channels().len(), 1);
88298829
assert!(nodes[0].node.list_usable_channels()[0].channel_id == chan_3.2);
88308830
}
8831+
8832+
#[test]
8833+
fn test_invalid_funding_tx() {
8834+
// Test that we properly handle invalid funding transactions sent to us from a peer.
8835+
//
8836+
// Previously, all other major lightning implementations had failed to properly sanitize
8837+
// funding transactions from their counterparties, leading to a multi-implementation critical
8838+
// security vulnerability (though we always sanitized properly, we've previously had
8839+
// un-released crashes in the sanitization process).
8840+
let chanmon_cfgs = create_chanmon_cfgs(2);
8841+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
8842+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
8843+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
8844+
8845+
nodes[0].node.create_channel(nodes[1].node.get_our_node_id(), 100_000, 10_000, 42, None).unwrap();
8846+
nodes[1].node.handle_open_channel(&nodes[0].node.get_our_node_id(), InitFeatures::known(), &get_event_msg!(nodes[0], MessageSendEvent::SendOpenChannel, nodes[1].node.get_our_node_id()));
8847+
nodes[0].node.handle_accept_channel(&nodes[1].node.get_our_node_id(), InitFeatures::known(), &get_event_msg!(nodes[1], MessageSendEvent::SendAcceptChannel, nodes[0].node.get_our_node_id()));
8848+
8849+
let (temporary_channel_id, mut tx, _) = create_funding_transaction(&nodes[0], 100_000, 42);
8850+
for output in tx.output.iter_mut() {
8851+
// Make the confirmed funding transaction have a bogus script_pubkey
8852+
output.script_pubkey = bitcoin::Script::new();
8853+
}
8854+
8855+
nodes[0].node.funding_transaction_generated_unchecked(&temporary_channel_id, tx.clone(), 0).unwrap();
8856+
nodes[1].node.handle_funding_created(&nodes[0].node.get_our_node_id(), &get_event_msg!(nodes[0], MessageSendEvent::SendFundingCreated, nodes[1].node.get_our_node_id()));
8857+
check_added_monitors!(nodes[1], 1);
8858+
8859+
nodes[0].node.handle_funding_signed(&nodes[1].node.get_our_node_id(), &get_event_msg!(nodes[1], MessageSendEvent::SendFundingSigned, nodes[0].node.get_our_node_id()));
8860+
check_added_monitors!(nodes[0], 1);
8861+
8862+
let events_1 = nodes[0].node.get_and_clear_pending_events();
8863+
assert_eq!(events_1.len(), 0);
8864+
8865+
assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().len(), 1);
8866+
assert_eq!(nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap()[0], tx);
8867+
nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().clear();
8868+
8869+
confirm_transaction_at(&nodes[1], &tx, 1);
8870+
check_added_monitors!(nodes[1], 1);
8871+
let events_2 = nodes[1].node.get_and_clear_pending_msg_events();
8872+
assert_eq!(events_2.len(), 1);
8873+
if let MessageSendEvent::HandleError { node_id, action } = &events_2[0] {
8874+
assert_eq!(*node_id, nodes[0].node.get_our_node_id());
8875+
if let msgs::ErrorAction::SendErrorMessage { msg } = action {
8876+
assert_eq!(msg.data, "funding tx had wrong script/value or output index");
8877+
} else { panic!(); }
8878+
} else { panic!(); }
8879+
assert_eq!(nodes[1].node.list_channels().len(), 0);
8880+
}

0 commit comments

Comments
 (0)