Skip to content

fuzz-tests: Add a test for fundee_channel() #8411

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

Chand-ra
Copy link

fundee_channel() in openingd/openingd.c is responsible for handling incoming open_channel messages from a peer. Since it deals with external input, add a test for it.

Checklist

Before submitting the PR, ensure the following tasks are completed. If an item is not applicable to your PR, please mark it as checked:

  • The changelog has been updated in the relevant commit(s) according to the guidelines.
  • Tests have been added or modified to reflect the changes.
  • Documentation has been reviewed and updated as needed.
  • Related issues have been listed and linked, including any that this PR closes.

Chandra Pratap added 2 commits July 14, 2025 12:28
Changelog-None: `fundee_channel()` in `openingd/openingd.c` is
responsible for handling incoming `open_channel` messages from a
peer. Since it deals with external input, add a test for it.
Add a minimal input set as a seed corpus for the newly introduced
test. This leads to discovery of interesting code paths faster.
Copy link
Contributor

@morehouse morehouse left a comment

Choose a reason for hiding this comment

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

I think the crash is probably happening because the initial state we use may have certain developer flags enabled. We should make sure we're using flags that would be used in production.

@@ -72,5 +72,17 @@ FUZZ_COMMON_OBJS := \
$(FUZZ_TARGETS_OBJS): $(COMMON_HEADERS) $(WIRE_HEADERS) $(COMMON_SRC)
$(FUZZ_TARGETS_BIN): $(LIBFUZZ_OBJS) $(FUZZ_COMMON_OBJS) $(BITCOIN_OBJS)

tests/fuzz/fuzz-open_channel: common/shutdown_scriptpubkey.o \
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: let's put this rule up by the other target-specific ones

{
struct state *state = talz(ctx, struct state);

state->developer = fromwire_bool(cursor, max);
Copy link
Contributor

Choose a reason for hiding this comment

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

We should always set developer = false, since we're only interested in bugs that happen in production.

= state->upfront_shutdown_script[REMOTE]
= NULL;

fromwire_bitcoin_outpoint(cursor, max, &state->funding);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we setting the funding outpoint here? Shouldn't this be set later by the funding_create message?

&state->minimum_depth,
&state->min_feerate, &state->max_feerate,
&state->dev_force_tmp_channel_id,
&state->allowdustreserve,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is probably one immediate cause of the crash -- this lets the fuzzer set allowdustreserve = true, which bypasses the checks needed to sanitize dust limits and channel reserves.

allowdustreserve is a developer option that we should probably always set to false for this test.

u8 remaining_len = fromwire_u8(cursor, max);
if (remaining_len > *max)
remaining_len = *max;
towire_u8_array(&openingd_init_msg, *cursor, remaining_len);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need to be more careful than to just populate state with random values. At the very least there are several developer options we should probably disable. And there might be other constraints we need to satisfy.

}

static u8 *create_open_channel_msg(const tal_t *ctx, const u8 **cursor, size_t *max, struct state *state)
{
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we should be doing any of the validation in this function. The open_channel message comes from the peer, which means it could contain anything. We should basically use raw fuzzer-generated data for the message.

Copy link
Author

Choose a reason for hiding this comment

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

Right, that's what I did initially but that approach took too long for the fuzzer to reach deeper code paths in the target function. By doing it this way, the fuzzer can achieve new coverage quicker while still retaining the ability to tweak the message.

Comment on lines +192 to +195
/* These have the same definitions as the original peer_reed and peer_write.
* We reiterate these here because we want them to use test_sync_write and
* test_sync_read.
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we're overriding peer_read and peer_write, we can just call test_sync_read and test_sync_write directly here for clarity.

struct amount_sat reserve = amount_sat(100);
u32 mindepth = 10;
return towire_openingd_got_offer_reply(ctx, NULL, NULL, NULL,
&reserve, mindepth);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should just use reserve=NULL for now, which will use the default of 1% of the channel capacity.

assert(false && "Too many HSMD writes!");
}
else if (fd == PEER_FD)
{
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like we could also get in the case from negotiation_failed, and in that scenario there wouldn't be an accept_channel message to send.

Comment on lines +70 to +71
return towire_funding_created(ctx, &state->channel_id,
&state->funding.txid, state->funding.n, &sig.s);
Copy link
Contributor

Choose a reason for hiding this comment

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

funding_created is a message sent by the peer and could contain anything. Ideally we would use fuzzer-generated data for this message, so we can check if the peer can crash us from this message.

In fact it looks like the peer can send other non-funding-created messages to fundee_channel as well, which we should also try here.

Of course to fuzz deeper in the state machine we also want valid funding_created messages to be created sometimes. So there's an opportunity for some optimization here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants