From 141ac25fc06dfd256748fa2b3acd93754ca0742f Mon Sep 17 00:00:00 2001 From: alosafuzz Date: Thu, 29 Oct 2026 07:49:33 -0600 Subject: [PATCH] Bound declared element counts against remaining buffer in from_wire The typed-data deserializers read an element/field count from the wire and then allocate (new[] / reserve) from it before reading any element data, without bounding the declared count against the bytes remaining in the message. A small message that declares a very large count therefore drives a correspondingly large allocation. Since each encoded element occupies at least one byte on the wire, a valid count can never exceed the bytes remaining, so clamp the declared count to buf.size() before allocating. This is a no-op for well-formed messages. Affected sites: the array decode in pvaproto.h (shared_array), and the struct child-count reserve and the StructA/UnionA/AnyA array decoders in dataencode.cpp. --- src/dataencode.cpp | 12 ++++++++++++ src/pvaproto.h | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/src/dataencode.cpp b/src/dataencode.cpp index 066cd3287..b20e9b384 100644 --- a/src/dataencode.cpp +++ b/src/dataencode.cpp @@ -153,6 +153,9 @@ void from_wire(Buffer& buf, std::vector& descs, TypeStore& cache, uns { auto& fld = descs.back(); + // bound child count to bytes remaining before reserving (see note in pvaproto.h) + if(nfld.size > (size_t)buf.size()) + nfld.size = buf.size(); fld.miter.reserve(nfld.size); } @@ -618,6 +621,9 @@ void from_wire_field(Buffer& buf, TypeStore& ctxt, const FieldDesc* desc, const case TypeCode::StructA:{ Size alen{}; from_wire(buf, alen); + // bound element count to bytes remaining before allocating (see note in pvaproto.h) + if(alen.size > (size_t)buf.size()) + alen.size = buf.size(); shared_array arr(alen.size); std::shared_ptr etype(store->top->desc, &desc->members[0]); // alias @@ -635,6 +641,9 @@ void from_wire_field(Buffer& buf, TypeStore& ctxt, const FieldDesc* desc, const case TypeCode::UnionA: { Size alen{}; from_wire(buf, alen); + // bound element count to bytes remaining before allocating (see note in pvaproto.h) + if(alen.size > (size_t)buf.size()) + alen.size = buf.size(); shared_array arr(alen.size); auto cdesc = &desc->members[0]; @@ -667,6 +676,9 @@ void from_wire_field(Buffer& buf, TypeStore& ctxt, const FieldDesc* desc, const case TypeCode::AnyA:{ Size alen{}; from_wire(buf, alen); + // bound element count to bytes remaining before allocating (see note in pvaproto.h) + if(alen.size > (size_t)buf.size()) + alen.size = buf.size(); shared_array arr(alen.size); for(auto& elem : arr) { diff --git a/src/pvaproto.h b/src/pvaproto.h index c9505e808..396f20484 100644 --- a/src/pvaproto.h +++ b/src/pvaproto.h @@ -528,6 +528,11 @@ void from_wire(Buffer& buf, shared_array& varr) { Size slen{}; from_wire(buf, slen); + // A valid array cannot contain more elements than there are bytes remaining in the + // message (each element occupies >= 1 byte on the wire). Bound the declared count + // before allocating so a crafted count cannot drive an unbounded allocation. + if(slen.size > (size_t)buf.size()) + slen.size = buf.size(); shared_array arr(slen.size); if(std::is_pod::value) {