Skip to content

smite-ir: add malformation parameter to funding_created - #235

Open
NishantBansal2003 wants to merge 4 commits into
lnfuzz:masterfrom
NishantBansal2003:semantic-field-malform
Open

NishantBansal2003 wants to merge 4 commits into
lnfuzz:masterfrom
NishantBansal2003:semantic-field-malform

Conversation

@NishantBansal2003

@NishantBansal2003 NishantBansal2003 commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

ref: #178

Depends-on: #212

Add a malformation parameter to SendFundingCreated that overwrites an allowlisted field of funding_created before sending it to the target. Some fields currently only contain valid values, so this allows the IR to exercise their invalid values as well.

Oracle state is updated based on the message actually sent to the target, after applying the malformation. If the malformed signature cannot be decoded, we send only the invalid signature without marking the state as desynced, since the target should simply reject it as an invalid signature rather than behave differently.

The generator produces only non-malformed funding_created messages, OperationParamMutator adds malformations using repeated bytes, random bytes, or interesting integers matching the field's exact width.

I verified it, and smite is now able to detect the bug disclosed in CLN funding_txid==0 assertion crash

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Some message fields are computed rather than supplied
as IR variables, so no mutator can give them invalid values.
Add Malformation to overwrite such a field in the encoded
message before sending.

MalformableField and MessageType::malformable_fields()
live in bolt next to the codecs.

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
Add an optional Malformation to SendFundingCreated
so the parameter mutator can overwrite funding_txid,
funding_output_index or signature. It can set, replace
or clear the malformation, using random bytes, a
repeated byte or an interesting integer.

FundingCreated now lists these fields and their offsets.
The executor does not apply the malformation yet.

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
The executor now applies the malformation before building
the commitment, so the channel it tracks matches what was
sent. A malformed outpoint changes which channel is tracked
and marks the outpoint invalid. A malformed signature is sent
unchanged and marks the signature invalid, even if it no longer
decodes.

Signed-off-by: Nishant Bansal <nishant.bansal.282003@gmail.com>
@NishantBansal2003
NishantBansal2003 marked this pull request as ready for review September 17, 2026 09:38
Comment thread smite/src/bolt.rs
Comment on lines +141 to +158
/// A message field whose encoded bytes can be overwritten before the message
/// is sent.
///
/// These identify fields for which the IR can only construct valid values, so
/// invalid values can only be put on the wire by overwriting their encoded
/// bytes. All other fields are reachable through the IR's parameters.
///
/// Offsets include the 2-byte message type prefix and therefore index directly
/// into the encoded message.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MalformableField {
/// Field name to overwrite.
pub name: &'static str,
/// Byte offset in the encoded message, including the message type prefix.
pub offset: u16,
/// Field length in bytes.
pub len: u16,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AFIK we haven't mentioned the IR in bolts crate until this PR. I think it would be better if bolts had no IR knowledge.

Suggested change
/// A message field whose encoded bytes can be overwritten before the message
/// is sent.
///
/// These identify fields for which the IR can only construct valid values, so
/// invalid values can only be put on the wire by overwriting their encoded
/// bytes. All other fields are reachable through the IR's parameters.
///
/// Offsets include the 2-byte message type prefix and therefore index directly
/// into the encoded message.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MalformableField {
/// Field name to overwrite.
pub name: &'static str,
/// Byte offset in the encoded message, including the message type prefix.
pub offset: u16,
/// Field length in bytes.
pub len: u16,
}
/// Location of a fixed-offset field within an encoded message.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldSpan {
/// Field name as written in the BOLT spec.
pub name: &'static str,
/// Byte offset in the encoded message, including the message type prefix.
pub offset: u16,
/// Field length in bytes.
pub len: u16,
}

Comment on lines +46 to +50
pub const MALFORMABLE_FIELDS: &'static [MalformableField] = &[
Self::FUNDING_TXID_FIELD,
Self::FUNDING_OUTPUT_INDEX_FIELD,
Self::SIGNATURE_FIELD,
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should live inside the malform.rs, since the fields that are unreachable depends on the IR's operation, not on the codecs.

Suggested change
pub const MALFORMABLE_FIELDS: &'static [MalformableField] = &[
Self::FUNDING_TXID_FIELD,
Self::FUNDING_OUTPUT_INDEX_FIELD,
Self::SIGNATURE_FIELD,
];

assert!(state.is_funding_outpoint_valid);
assert!(state.sent_invalid_signature);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These two returns aren't being covered in the tests if they're also applying correctly the malformation.

// Without both the recorded `open_channel` and the peer's `accept_channel`
    // we cannot build the commitment to sign, so fall back to an unsigned
    // `funding_created` and leave `channel_states` untouched.
    let Some(pending) = negotiations.get(&temporary_channel_id) else {
        return Ok(encoded);
    };
    let open_channel = &pending.open_channel;
    let Some(accept_channel) = pending.accept_channel.as_ref() else {
        return Ok(encoded);
    };

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