From 21162584b4b3e0c9f192f460d0d946acd99474a7 Mon Sep 17 00:00:00 2001 From: Filyus Date: Sun, 4 Oct 2026 13:47:04 +0500 Subject: [PATCH] Bound the orientation count of portable tex coord prediction MeshPredictionSchemeTexCoordsPortableDecoder::DecodePredictionData() reads the number of orientations as an int32_t from the stream and checks only that it is not negative. The count then sizes the orientation storage and drives a loop that cannot stop early, since RAnsBitDecoder::DecodeNextBit() returns false for a zero bit and for an exhausted buffer alike. A 224-byte stream declaring about two billion orientations, found by fuzzing the decoder, takes 2 s to decode with a release build of 1.5.7 and 14 s with main under AddressSanitizer, and reports success. Each predicted entry consumes at most one orientation, so a count above the number of entries can never be used whatever the stream contains. The count is now refused when it exceeds the size of the data-to-corner map. The bound is structural rather than a guess from the remaining buffer size: the orientations are rANS-coded, and a run of equal ones takes far less than a bit each. TestTexCoordOrientationCountIsBounded decodes that stream and expects a failure, which now comes in 1 ms. --- ...ction_scheme_tex_coords_portable_decoder.h | 10 ++++++ src/draco/compression/decode_test.cc | 31 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_decoder.h b/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_decoder.h index 83d496639..ee9c5478d 100644 --- a/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_decoder.h +++ b/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_decoder.h @@ -121,6 +121,16 @@ bool MeshPredictionSchemeTexCoordsPortableDecoder< if (!buffer->Decode(&num_orientations) || num_orientations < 0) { return false; } + // Each predicted entry uses at most one orientation, so the count cannot be + // larger than the number of entries. The loop below cannot stop early, + // because DecodeNextBit() returns false both for a zero bit and at the end + // of the buffer. + const std::vector *const data_to_corner_map = + this->mesh_data().data_to_corner_map(); + if (data_to_corner_map == nullptr || + static_cast(num_orientations) > data_to_corner_map->size()) { + return false; + } predictor_.ResizeOrientations(num_orientations); bool last_orientation = true; RAnsBitDecoder decoder; diff --git a/src/draco/compression/decode_test.cc b/src/draco/compression/decode_test.cc index 8f3e7f4e9..eb29b4e08 100644 --- a/src/draco/compression/decode_test.cc +++ b/src/draco/compression/decode_test.cc @@ -242,4 +242,35 @@ TEST_F(DecodeTest, TestSkipAttributeTransformUniqueId) { << std::endl; } +TEST_F(DecodeTest, TestTexCoordOrientationCountIsBounded) { + // A 224-byte EdgeBreaker mesh whose portable texture coordinate prediction + // declares about two billion orientations. The decoder rejects the count + // because there are far fewer entries to use them. + const uint8_t data[] = { + 0x44, 0x52, 0x41, 0x43, 0x4f, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x0c, 0x02, 0x0b, 0x00, 0x00, 0x03, 0x5f, 0x4b, 0x15, 0x01, 0x01, + 0x10, 0x55, 0x04, 0x8a, 0xac, 0xa4, 0x46, 0x55, 0x04, 0x8a, 0xac, 0xa4, + 0x46, 0x03, 0xff, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, + 0x00, 0x09, 0x03, 0x00, 0x00, 0x02, 0x01, 0x03, 0x09, 0x02, 0x00, 0x01, + 0x02, 0x01, 0x01, 0x09, 0x03, 0x00, 0x02, 0x03, 0x01, 0x01, 0x01, 0x00, + 0x03, 0x03, 0x01, 0x30, 0x01, 0x10, 0x03, 0x00, 0x28, 0x96, 0x8e, 0x08, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x3f, 0x14, 0x05, 0x01, 0x01, 0x00, 0x0d, 0x03, 0x55, 0x0d, 0xad, + 0x12, 0x07, 0x01, 0x08, 0x17, 0x01, 0x18, 0x08, 0x8d, 0x82, 0x72, 0xc3, + 0xb7, 0x3c, 0x83, 0x8d, 0x06, 0xbc, 0xfc, 0xbf, 0xbf, 0xe5, 0xfb, 0xbf, + 0xbf, 0xfc, 0xcb, 0xbf, 0xba, 0x81, 0xfc, 0xcb, 0x3f, 0x9a, 0xfc, 0xcb, + 0xff, 0xfb, 0xbf, 0x7f, 0x02, 0xf6, 0xfb, 0xcb, 0x3e, 0x2e, 0xff, 0xef, + 0xff, 0xfe, 0xf2, 0x0f, 0x0c, 0x00, 0x00, 0x3e, 0x01, 0x02, 0xc0, 0x40, + 0x00, 0x00, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x0c, 0x06, 0x03, 0x01, + 0x01, 0x03, 0x01, 0x01, 0x40, 0x01, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, + 0x07, 0x00, 0x00, 0xff, 0x02, 0xa1, 0x41, 0x0c, + }; + draco::DecoderBuffer buffer; + buffer.Init(reinterpret_cast(data), sizeof(data)); + draco::Decoder decoder; + ASSERT_FALSE(decoder.DecodeMeshFromBuffer(&buffer).ok()); +} + } // namespace