Skip to content

fuzz-tests: add test for amount-{sat, msat} arithmetic #8298

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

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

Conversation

Chand-ra
Copy link

The fuzz-amount test doesn't fuzz the arithmetic operations for struct amount_sat and struct amount_msat. Add a test for the same.

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.

@Chand-ra
Copy link
Author

Hey @morehouse , running this test throws the following errors:

  • common/amount.c:359:18: runtime error: -nan is outside the range of representable values of type 'unsigned long'
  • common/amount.c:345:23: runtime error: -inf is outside the range of representable values of type 'unsigned long'
  • common/amount.c:345:23: runtime error: -1.10313e+217 is outside the range of representable values of type 'unsigned long'

among a few others, which are probably due to faulty harness logic. The above failures happen in the following two functions:

WARN_UNUSED_RESULT bool amount_msat_scale(struct amount_msat *val,
					  struct amount_msat msat,
					  double scale)
{
	double scaled = msat.millisatoshis * scale;

	/* If mantissa is < 64 bits, a naive "if (scaled >
	 * UINT64_MAX)" doesn't work.  Stick to powers of 2. */
	if (scaled >= (double)((u64)1 << 63) * 2)
		return false;
345	val->millisatoshis = scaled;
	return true;
}

WARN_UNUSED_RESULT bool amount_sat_scale(struct amount_sat *val,
					 struct amount_sat sat,
					 double scale)
{
	double scaled = sat.satoshis * scale;

	/* If mantissa is < 64 bits, a naive "if (scaled >
	 * UINT64_MAX)" doesn't work.  Stick to powers of 2. */
	if (scaled >= (double)((u64)1 << 63) * 2)
		return false;
359	val->satoshis = scaled;
	return true;
}

Has the fuzzer found an actual bug or is it just another impossible edge case?

@morehouse
Copy link
Contributor

Has the fuzzer found an actual bug or is it just another impossible edge case?

I think technically these are bugs -- amount_msat_scale and amount_sat_scale are supposed to return false when scaling fails and already do this for the positive-overflow case. Since there's no usage comment requiring scale to be positive, probably the functions should handle negative overflow as well.

In practice, all current usages of these functions in the codebase have positive values for scale, so the bug can never manifest in the existing codebase.

@Chand-ra
Copy link
Author

In practice, all current usages of these functions in the codebase have positive values for scale, so the bug can never manifest in the existing codebase.

Makes me wonder why scale's type isn't unsigned in the first place...

@morehouse
Copy link
Contributor

Makes me wonder why scale's type isn't unsigned in the first place...

Probably because there's no way to represent an unsigned float.

The fuzz-amount test doesn't fuzz the arithmetic operations
for `struct amount_sat` and `struct amount_msat`. Add a test
for the same.
@Chand-ra Chand-ra force-pushed the fuzz-amount-arith branch from 2e97725 to 108f7a6 Compare May 22, 2025 11:23
An assertion failure occurs in amount_msat_sub_fee() due to overlow
of parameters. Add a test in tests/test_askrene.py to try and
trigger this failure through a user-level command.
@Chand-ra
Copy link
Author

Chand-ra commented Jun 2, 2025

Hey @morehouse,

As we discussed over email, I spent some time last week investigating the potential bug caused by an assertion failure in amount_msat_sub_fee() to determine whether it could be triggered externally. I grepped the codebase for all callers of amount_msat_sub_fee() and found that the only caller resides in plugins/askrene/refine.c. This, in turn, ultimately traces back to askrene’s getroutes command.

To test this path, I added a user-level Python test in tests/test_askrene.py to try passing the error-causing parameters to amount_msat_sub_fee(). I found that any parameter values that get too large are truncated to a set of ‘safe values’, making it impossible to reproduce the breakage through this command.

In summary, it doesn’t appear that any external message can trigger the failure seen in the fuzz test. I’ve pushed the test in case you’d like to take a look.

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