Reject trailing bytes in BFT transmission payloads - #4344
Conversation
There was a problem hiding this comment.
Thx!
I think this may not be backwards compatible.
When e.g. only 50% of the network upgraded to this version, a transaction in a proposal may not be deserialized anymore, making it impossible for 66% of proposals to be certified. This would have to be guarded by the most recent ConsensusVersion.
Doing ConsensusVersion guarded checks during block creation is easy, doing it in snarkOS is extra tricky because different nodes may be at different block heights while they're proposing asynchonrously for different consensus rounds.
So what can be done is:
- In
fn propose_batchwe stop including these atCONSENSUS_HEIGHT(ConsensusVersion::whatever) - In 'fn process_batch_propose
we stop signing atCONSENSUS_HEIGHT(ConsensusVersion::whatever)+50`, because we can be sure all nodes reached that height by then
There is some prior art for this in the code history but we also pruned some of this ductapy code over time. I'd also welcome an abstracted function which handles activating logic in both paths at the two different block heights.
We have an upgrade-test in merge-workflow which may help to simulate this, but it's not practical to simulate every possible edge case transaction - CI would take forever.
--
The same problem as above may occur in the future if we ever change the max transaction size. One way to make that more robust, is to not pick the latest max transaction size, but:
- in the
fn propose_batchflow use the one atConsensusVersion(current_height) - in the
fn process_batch_proposeflow allow the max size frommax(max_transaction_sizes[(ConsensusVersion(current_height-100))..]
--
At this point this whole enhancement seems incredibly complex, so please do share your 2 cents on if it's worth it from a maintainability perspective. Maybe the attack vector is not worth closing at this point or there is a simpler approach which solves most of the issues.
|
Thank you for the detailed response @vicsn! I want to separate two concerns: max transaction size, and trailing bytes / non-canonical transmission payloads. Max transaction size (the
|
Motivation
BFT transmission validation previously accepted raw transaction and solution buffers that contained a valid serialized object followed by trailing bytes. The checksum was computed over the full raw buffer, but the payload was later canonicalized to
Data::Object, stripping the trailing bytes and causing future checksum computations to differ for the sameTransmissionID.This PR adds strict deserialization for BFT transaction and solution transmissions so raw buffers must contain exactly one serialized object. Padded buffers are rejected before canonicalization.
Test Plan
Unit tests were added for the new strict deserialization helpers. Aside from that we will be relying on the existing tests.
Documentation
N/A
Backwards compatibility
From what I can tell this is not expected to cause any backward compatibility issues.