Skip to content

common: saturate marginal_feerate() instead of overflowing - #9365

Open
morehouse wants to merge 1 commit into
ElementsProject:masterfrom
morehouse:fix_marginal_feerate_overflow
Open

common: saturate marginal_feerate() instead of overflowing#9365
morehouse wants to merge 1 commit into
ElementsProject:masterfrom
morehouse:fix_marginal_feerate_overflow

Conversation

@morehouse

Copy link
Copy Markdown
Contributor

marginal_feerate() computed current_feerate * 1.1 as a double and converted the result back to u32. Since current_feerate is chosen by the peer via open_channel or update_fee, they could choose an absurdly high value that overflows u32 after the computation. UBSan reports:

common/fee_states.c:179:10: runtime error: 4.72446e+09 is outside the range of representable values of type 'unsigned int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior common/fee_states.c:179:10

Do the arithmetic with u64 and saturate at UINT32_MAX to avoid the undefined behavior.

Found by fuzzing with smite.

marginal_feerate() computed current_feerate * 1.1 as a double and
converted the result back to u32.  Since current_feerate is chosen by
the peer in open_channel or update_fee, they could choose an absurdly
high value that overflows u32 after the computation.  UBSan reports:

    common/fee_states.c:179:10: runtime error: 4.72446e+09 is outside the range of representable values of type 'unsigned int'
    SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior common/fee_states.c:179:10

Do the arithmetic with u64 and saturate at UINT32_MAX to avoid the
undefined behavior.

Found by fuzzing with smite.

Changelog-Fixed: JSON-RPC: `listpeerchannels` no longer derives `receivable_msat` from an overflowed fee estimate when the peer sets an absurd `feerate_per_kw`.

@Andezion Andezion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The fix correctly addresses the UBSan-reported issue, the arithmetic is correct (I verified it numerically), and the added tests cover the exact boundary condition

Comment thread common/fee_states.c
if (current_feerate > maxfeerate)
return current_feerate * 1.1;
if (current_feerate > maxfeerate) {
u64 marginal = ((u64)current_feerate * 11) / 10;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is it intentional that the fixed version rounds down ((current_feerate * 11) / 10 truncates), while the old double version also truncated on conversion to u32? I confirmed both truncate the same way for in-range values, so behavior should be unchanged for normal feerates - just confirming this was checked, since it affects the exact fee charged to peers, not just an internal-only value :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was simply trying to preserve the existing behavior. However, I don't think it really matters whether we use floor, round, or ceil here, since this function only influences the "fee spike buffer", whose size is a fuzzy concept to begin with.

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