Skip to content

Make wrap decoding exact when the sum leaves int32 - #1238

Merged
igorvytyaz merged 2 commits into
google:mainfrom
Filyus:exact-wrap-decoding
Oct 9, 2026
Merged

igorvytyaz merged 2 commits into
google:mainfrom
Filyus:exact-wrap-decoding

Conversation

@Filyus

@Filyus Filyus commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

PredictionSchemeWrapDecodingTransform::ComputeOriginalValue() adds the predicted value and the correction in uint32_t, to avoid signed overflow on malformed input, and then wraps once by the span max_dif. The encoder wraps each correction into [min_correction, max_correction], half the span, so the exact sum is at most one span outside [min, max] and one wrap is enough, but only if the sum is exact. When min and max are far apart the uint32_t sum itself wraps, the single wrap lands 2^32 away from the coded value, and every later prediction reads the wrong number. The decoder then either rejects the stream or decodes different values, for a stream its own encoder wrote.

Doing the addition in int64_t keeps it exact for valid streams and still free of overflow for malformed ones. Sums that fit in int32 decode exactly as before.

TestTexCoordPredictionThatWrapsRoundTrips builds six points with uint16 positions under constrained multi-parallelogram prediction and int32 texture coordinates under portable prediction, encodes them with EdgeBreaker at speeds 0/4 into a 911-byte stream and decodes it. Before this change the decode fails with "Failed to decode point attributes". After it, every decoded point carries a position and texture coordinate an input point carried. The mesh was found by fuzzing an independent implementation of the bitstream, which decodes the same stream.

PredictionSchemeWrapDecodingTransform::ComputeOriginalValue() adds the
predicted value and the correction in uint32_t, to avoid signed overflow
on malformed input, and then wraps once by the span max_dif. The encoder
wraps each correction into [min_correction, max_correction], half the
span, so the exact sum is at most one span outside [min, max] and one
wrap is enough, but only if the sum is exact. When min and max are far
apart the uint32_t sum itself wraps, the single wrap lands 2^32 away
from the coded value, and every later prediction reads the wrong number.
The decoder then either rejects the stream or decodes different values,
for a stream its own encoder wrote.

Doing the addition in int64_t keeps it exact for valid streams and still
free of overflow for malformed ones. Sums that fit in int32 decode
exactly as before.

TestTexCoordPredictionThatWrapsRoundTrips builds six points with uint16
positions under constrained multi-parallelogram prediction and int32
texture coordinates under portable prediction, encodes them with
EdgeBreaker at speeds 0/4 into a 911-byte stream and decodes it. Before
this change the decode fails with "Failed to decode point attributes".
After it, every decoded point carries a position and texture coordinate
an input point carried. The mesh was found by fuzzing an independent
implementation of the bitstream, which decodes the same stream.
igorvytyaz
igorvytyaz previously approved these changes Oct 9, 2026
@igorvytyaz
igorvytyaz merged commit c301b6a into google:main Oct 9, 2026
37 checks passed
@igorvytyaz

Copy link
Copy Markdown
Collaborator

Thank you for making this contribution to Draco.

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