Skip to content

cryptonote_basic: fix add_extra_nonce_to_tx_extra() length#10220

Merged
tobtoht merged 1 commit intomonero-project:masterfrom
jeffro256:fix_extra_nonce_len
Mar 1, 2026
Merged

cryptonote_basic: fix add_extra_nonce_to_tx_extra() length#10220
tobtoht merged 1 commit intomonero-project:masterfrom
jeffro256:fix_extra_nonce_len

Conversation

@jeffro256
Copy link
Contributor

Serialization/deserialization code in tx_extra.h treats the nonce length as a varint, but cryptonote::add_extra_nonce_to_tx_extra() writes the nonce length as a raw unsigned 8-bit integer. For nonce sizes in $[128, 256)$, these two sections diverge. This commit amends cryptonote::add_extra_nonce_to_tx_extra() to match the serialization code.

Issue identified by the Monero Oxide project and reporters.

@jeffro256
Copy link
Contributor Author

Unit test failure is unrelated.

@jeffro256
Copy link
Contributor Author

Fixed test cases up and added tests for tx_extra_merge_mining_tag and add_mm_merkle_root_to_tx_extra().

{
extra = extra_prefix;
nonce.resize(nonce_size);
memset(&nonce[0], '%', nonce.size());
Copy link
Collaborator

Choose a reason for hiding this comment

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

UB if nonce_size = 0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the record, this probably is fine in practice since operator[]() will return some garbage pointer, and memset() is passed a length of 0, but you're absolutely right that operator[]() when pos < size() is false triggers UB (At least until C++26 with a "hardened implementation", under which scenario it becomes a contract violation, not UB).

Will update

subtest_varint_byte_size_for_single_value(1, n);
}

for (T n = 128; n < 255; ++n)
Copy link
Contributor

Choose a reason for hiding this comment

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

n <= 255 maybe?

Copy link
Contributor

@SChernykh SChernykh Feb 24, 2026

Choose a reason for hiding this comment

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

But be careful about type bounds to not make it an infinite loop. So (n <= 255) && n is better, to be sure that it doesn't get stuck.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually nevermind, I see that you check 255 and 65535 separately further down in the code.

subtest_varint_byte_size_for_single_value(2, n);
}

for (T n = 16384; n < 65535; ++n)
Copy link
Contributor

Choose a reason for hiding this comment

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

n <= 65535

Reviewed-by: selsta <selsta@sent.at>
Reviewed-by: SChernykh
@jeffro256
Copy link
Contributor Author

Sorry, I made one more change: I added an if branch before the mempy() so that tx_extra[start_pos] isn't called when the nonce is empty (and thus start_pos == tx_extra.size()). This triggers UB when extra_nonce.empty() and actually trips a debug assertion on newer versions of glibc++.

@tobtoht tobtoht merged commit 2404f1b into monero-project:master Mar 1, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants