Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix various issues found in full_stack_target fuzzing #2808

Merged

Conversation

TheBlueMatt
Copy link
Collaborator

#2804 reported a message-processing-reachable unwrap, which is concerning, but more generally it reported a serious coverage gap in our full_stack_target fuzzer. This fuzzer should be our first line of defense against such issues, but sadly was using a predefined config object which left us not testing cases that required specific config flags. This fixes #2804 (in an imo cleaner way than #2805, at least for downstream devs even if not in LDK) as well as a number of other quite minor debug and overflow issues found after I updated the full_stack_target to test different config flags.

If we get a `Feature` object which has excess zero bytes, we
shouldn't consider it a different `Feature` from another with the
same bits set, but no excess zero bytes. Here we fix both the
`Hash` and `PartialEq` implementation for `Features` to ignore
excess zero bytes.
When we or our counterparty are updating the fees on the channel,
we currently check that the resulting balance is sufficient not
only to meet the reserve threshold, but also not push it below
dust. This isn't required in the BOLTs and may lead to spurious
force-closures (which would be a bit safer, but reserve should
always exceed the dust threshold).

Worse, the current logic is broken - it compares the output value
in *billionths of satoshis* to the dust limit in satoshis. Thus,
the code is borderline dead anyway, but can overflow for channels
with several million Bitcoin, causing the fuzzer to get mad (and
lead to spurious force-closures for few-billion-dollar channels).
When contest delays are >= 0x8000, script pushes require an extra
byte to avoid being interpreted as a negative int. Thus, for
channels with CSV delays longer than ~7.5 months we may generate
transactions with slightly too little fee. This isn't really a huge
deal, but we should prefer to be conservative here, and slightly
too high fee in the general case is better than slightly too little
fee in other cases.
If we try to open a channel with a peer that is disconnected (but
with which we have some other channels), we'll end up with an
unfunded channel which will lead to a panic when the peer
reconnects. Here we drop this debug assertion without bother to add
a new test, given this behavior will change in a PR very soon.
If a peer provides a feerate which nears `u32::MAX`, we may
overflow calculating the dust buffer feerate, leading to spuriously
keeping non-anchor channels open when they should be force-closed.
@codecov-commenter
Copy link

codecov-commenter commented Dec 29, 2023

Codecov Report

Attention: 5 lines in your changes are missing coverage. Please review.

Comparison is base (4deb263) 88.49% compared to head (bceca1e) 88.73%.
Report is 14 commits behind head on main.

❗ Current head bceca1e differs from pull request most recent head 7f24e83. Consider uploading reports for the commit 7f24e83 to get more accurate results

Files Patch % Lines
lightning/src/ln/channel.rs 88.57% 4 Missing ⚠️
lightning/src/ln/features.rs 96.55% 0 Missing and 1 partial ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2808      +/-   ##
==========================================
+ Coverage   88.49%   88.73%   +0.23%     
==========================================
  Files         114      114              
  Lines       91935    95699    +3764     
  Branches    91935    95699    +3764     
==========================================
+ Hits        81359    84914    +3555     
- Misses       8097     8336     +239     
+ Partials     2479     2449      -30     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question, otherwise LGTM.

@@ -6884,7 +6894,7 @@ impl<SP: Deref> InboundV1Channel<SP> where SP::Target: SignerProvider {
channel_type.clone()
} else {
let channel_type = ChannelTypeFeatures::from_init(&their_features);
if channel_type != ChannelTypeFeatures::only_static_remote_key() {
if channel_type != channel_type_from_open_channel(msg) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is a bit confusing to me: why make it look like we'd consider msg after all, when in fact it would always result in ChannelTypeFeatures::only_static_remote_key()? Leaving it as-is seems more readable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I was trying to keep from making the "default channel type is only-static-remote-key" assumption copied in several places, but I agree this ended up being more awkward than not. Instead I moved the whole "what is the channel type given the message" thing into the new method and made it fallible. This no longer directly solves the issue, but rather actually handling the failures does, which is also kinda nice in that we will avoid giving users an event for a channel we can't use.

Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes look great! I have one small question and two suggestions for the comments.

lightning/src/ln/chan_utils.rs Show resolved Hide resolved
@@ -1876,7 +1872,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
if let Some(feerate) = outbound_feerate_update {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the description above it is written:

// may, at any point, increase _by_ at least 10 sat/vB (i.e 2530 sat/kWU) or 25%,
// whichever is higher.

which implies:

cmp::max(feerate_by_kw + 2530, feerate_plus_quarter)

Whereas, the line should read:

pub fn get_dust_buffer_feerate(&self, outbound_feerate_update: Option<u32>) -> u32 {
                // When calculating our exposure to dust HTLCs, we assume that the channel feerate
-               // may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%,
+               // may, at any point, increase to at least 10 sat/vB (i.e 2530 sat/kWU) or by 25%,
                // whichever is higher. This ensures that we aren't suddenly exposed to significantly

Since we are touching this function in this PR. I believe we should also address this small but significant comment change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, good catch! I'd like to actually address this by making the code match the comment, not the other way around, so I'll address this in a later PR - #2815

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

Copy link
Contributor

@wpaulino wpaulino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM after squash

lightning/src/ln/chan_utils.rs Show resolved Hide resolved
@TheBlueMatt
Copy link
Collaborator Author

Squashed.

tnull
tnull previously approved these changes Jan 8, 2024
Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

One nit, but feel free to land as is.

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@shaavan shaavan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK
The changes look great, and since the comment will be addressed in a follow-up PR, I believe this PR is good to go!

If we receive an `OpenChannel` message without a `channel_type`
with `manually_accept_inbound_channels` set, we will `unwrap()`
`None`.

This is uncommon these days as most nodes support `channel_type`,
but sadly is rather trivial for a peer to hit for those with manual
channel acceptance enabled.

Reported in and fixes lightningdevkit#2804. Luckily, the updated
`full_stack_target` has no issue reaching this issue quickly.
@TheBlueMatt
Copy link
Collaborator Author

Addressed the nit:

$ git diff-tree -U1 bceca1eed7aced011150f0f6ca7f0dd6fbe71d6a 7f24e833
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 5efef2e90..8e0ac2fdf 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -6172,10 +6172,7 @@ where
 		if self.default_configuration.manually_accept_inbound_channels {
-			let channel_type_res = channel::channel_type_from_open_channel(
-				&msg, &peer_state.latest_features, &self.channel_type_features()
-			);
-			let channel_type = match channel_type_res {
-				Ok(channel_type) => channel_type,
-				Err(e) =>
-					return Err(MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id)),
-			};
+			let channel_type = channel::channel_type_from_open_channel(
+					&msg, &peer_state.latest_features, &self.channel_type_features()
+				).map_err(|e|
+					MsgHandleErrInternal::from_chan_no_close(e, msg.temporary_channel_id)
+				)?;
 			let mut pending_events = self.pending_events.lock().unwrap();

@TheBlueMatt TheBlueMatt merged commit 3fbee85 into lightningdevkit:main Jan 8, 2024
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants