serialize: add LimitedVectorFormatter
What changed, and why it matters
This commit adds a new helper called LimitedVectorFormatter that lets Bitcoin Core reject serialized data containing vectors (lists of values) that are larger than an allowed maximum. It is a defensive building block, not a fix for an active bug. The commit itself does not change any existing network or file parsing code, so it cannot by itself prevent or cause any vulnerability.
No immediate action is required. Treat this as routine hardening. Future code reviews should verify that any newly introduced vector deserialization sites use LIMITED_VECTOR (or equivalent bounds checking) where appropriate, and that Limit values are chosen consistently with protocol or resource limits.
Security signals we found
Adds an explicit length-limit check during deserialization, which is a defensive hardening pattern
Mirrors an existing, trusted formatter (LimitedStringFormatter) already used for bounded string deserialization
No production code paths are changed; the new formatter is only a library addition and unit test
Evidence from the diff
The patch introduces LimitedVectorFormatter
Changed components
src/serialize.hsrc/test/serialize_tests.cppInspect captured patch +55 / −0
diff --git a/src/serialize.h b/src/serialize.h
index 2681b098..5d38a510 100644
--- a/src/serialize.h
+++ b/src/serialize.h
@@ -494,6 +494,7 @@ static inline Wrapper<Formatter, T&> Using(T&& t) { return Wrapper<Formatter, T&
#define VARINT(obj) Using<VarIntFormatter<VarIntMode::DEFAULT>>(obj)
#define COMPACTSIZE(obj) Using<CompactSizeFormatter<true>>(obj)
#define LIMITED_STRING(obj,n) Using<LimitedStringFormatter<n>>(obj)
+#define LIMITED_VECTOR(obj,n) Using<LimitedVectorFormatter<n>>(obj)
/** Serialization wrapper class for integers in VarInt format. */
template<VarIntMode Mode>
@@ -790,6 +791,35 @@ struct DefaultFormatter
static void Unser(Stream& s, T& t) { Unserialize(s, t); }
};
+/**
+ * Limited vector formatter. Throws an error if a vector is oversized.
+ */
+
+template<size_t Limit, class Formatter = DefaultFormatter>
+struct LimitedVectorFormatter
+{
+ template<typename Stream, typename V>
+ void Unser(Stream& s, V& v)
+ {
+ Formatter formatter;
+ v.clear();
+ size_t size = ReadCompactSize(s);
+ if (size > Limit) {
+ throw std::ios_base::failure("Vector length limit exceeded");
+ }
+ v.reserve(size);
+ for (size_t i = 0; i < size; ++i) {
+ v.emplace_back();
+ formatter.Unser(s, v.back());
+ }
+ }
+
+ template<typename Stream, typename V>
+ void Ser(Stream& s, const V& v)
+ {
+ VectorFormatter<Formatter>{}.Ser(s, v);
+ }
+};
diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp
index dd2c8cdd..9c2f16f0 100644
--- a/src/test/serialize_tests.cpp
+++ b/src/test/serialize_tests.cpp
@@ -9,6 +9,7 @@
#include <test/util/setup_common.h>
#include <util/strencodings.h>
+#include <algorithm>
#include <cstdint>
#include <string>
@@ -251,6 +252,30 @@ BOOST_AUTO_TEST_CASE(string_view)
BOOST_CHECK_EQUAL(sv, s);
}
+BOOST_AUTO_TEST_CASE(limited_vector)
+{
+ const std::vector<int> v = {1,2,3,4,-5,-6,-7,-8,-9,-10,10000,20000,-30000};
+
+ auto check = [&]<size_t N>() {
+ DataStream ss;
+ ss << v;
+ try {
+ std::vector<int> r;
+ ss >> LIMITED_VECTOR(r, N);
+ BOOST_CHECK_LE(r.size(), N);
+ BOOST_CHECK(std::ranges::equal(r, v));
+ } catch (const std::ios_base::failure&) {
+ BOOST_CHECK_GT(v.size(), N);
+ }
+ };
+ check.operator()<0>();
+ check.operator()<10>();
+ check.operator()<12>();
+ check.operator()<13>();
+ check.operator()<14>();
+ check.operator()<100>();
+}
+
BOOST_AUTO_TEST_CASE(class_methods)
{
int intval(100);
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.