From 777c9215350e4ff1118fded7ecc27fa51f21bf24 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 2 Jul 2026 18:40:52 -0700 Subject: [PATCH 1/2] Check for match of complex element types eg. Array of Structure must have Structure elements. --- src/factory/FieldCreateFactory.cpp | 8 ++++++-- testApp/misc/testSerialization.cpp | 31 +++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/factory/FieldCreateFactory.cpp b/src/factory/FieldCreateFactory.cpp index 561e8ee4..b584ad92 100644 --- a/src/factory/FieldCreateFactory.cpp +++ b/src/factory/FieldCreateFactory.cpp @@ -1564,7 +1564,9 @@ FieldConstPtr FieldCreate::deserialize(ByteBuffer* buffer, DeserializableControl throw std::invalid_argument("fixed and bounded structure array not supported"); // Type type = Type.structureArray; - StructureConstPtr elementStructure = std::tr1::static_pointer_cast(control->cachedDeserialize(buffer)); + StructureConstPtr elementStructure = std::tr1::dynamic_pointer_cast(control->cachedDeserialize(buffer)); + if(!elementStructure) + throw std::invalid_argument("Expected Structure"); // TODO use std::make_shared std::tr1::shared_ptr sp(new StructureArray(elementStructure)); Helper::cache(this, sp); @@ -1577,7 +1579,9 @@ FieldConstPtr FieldCreate::deserialize(ByteBuffer* buffer, DeserializableControl throw std::invalid_argument("fixed and bounded structure array not supported"); // Type type = Type.unionArray; - UnionConstPtr elementUnion = std::tr1::static_pointer_cast(control->cachedDeserialize(buffer)); + UnionConstPtr elementUnion = std::tr1::dynamic_pointer_cast(control->cachedDeserialize(buffer)); + if(!elementUnion) + throw std::invalid_argument("Expected Union"); // TODO use std::make_shared std::tr1::shared_ptr sp(new UnionArray(elementUnion)); Helper::cache(this, sp); diff --git a/testApp/misc/testSerialization.cpp b/testApp/misc/testSerialization.cpp index dcbef239..617586a7 100644 --- a/testApp/misc/testSerialization.cpp +++ b/testApp/misc/testSerialization.cpp @@ -849,11 +849,38 @@ void testFromString(int byteOrder) testOk1(_data->getSubFieldT("Y")->get()=="testing"); } +void testDeserializeTypeFail(const char *buf, size_t buflen) +{ + + std::vector bytes(buf, buf + buflen); + ByteBuffer B(&bytes[0], bytes.size(), EPICS_ENDIAN_BIG); + + try{ + FieldConstPtr ptr(getFieldCreate()->deserialize(&B, control)); + }catch(std::exception& e) { + testPass("Expected exception: %s", e.what()); + return; + } + testFail("Unexpected success? %d", __LINE__); +} + +void testMalformed() +{ + testDiag("testMalformed()"); + +#define CASE(STR) testDeserializeTypeFail(STR, sizeof(STR)-1) + // a structure array where the element type is not a structure + CASE("\x88\x83\x2b\x01\x01"); + // a union array where ... + CASE("\x89\x83\x2b\x01\x01"); +#undef CASE +} + } // end namespace MAIN(testSerialization) { - testPlan(234); + testPlan(236); flusher = new SerializableControlImpl(); control = new DeserializableControlImpl(); @@ -880,6 +907,8 @@ MAIN(testSerialization) { testFromString(EPICS_ENDIAN_BIG); testFromString(EPICS_ENDIAN_LITTLE); + testMalformed(); + delete buffer; delete control; delete flusher; From abc95982ed5d750b727e1ace7b944e7964b8ab9f Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Fri, 3 Jul 2026 18:54:35 -0700 Subject: [PATCH 2/2] bound deserialize() recursion --- src/factory/FieldCreateFactory.cpp | 22 +++++++++++++++++++++- src/factory/PVStructure.cpp | 3 +++ src/factory/PVStructureArray.cpp | 3 +++ src/factory/PVUnion.cpp | 3 +++ src/factory/PVUnionArray.cpp | 3 +++ src/factory/factoryPvt.h | 14 ++++++++++++++ testApp/misc/testSerialization.cpp | 24 +++++++++++++++++++++++- 7 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 src/factory/factoryPvt.h diff --git a/src/factory/FieldCreateFactory.cpp b/src/factory/FieldCreateFactory.cpp index b584ad92..c4ed7170 100644 --- a/src/factory/FieldCreateFactory.cpp +++ b/src/factory/FieldCreateFactory.cpp @@ -28,6 +28,8 @@ #include #include +#include "factoryPvt.h" + using std::tr1::static_pointer_cast; using std::size_t; using std::string; @@ -36,7 +38,6 @@ namespace epics { namespace pvData { size_t Field::num_instances; - struct Field::Helper { static unsigned hash(Field *fld) { std::ostringstream key; @@ -1476,6 +1477,7 @@ static int decodeScalar(int8 code) FieldConstPtr FieldCreate::deserialize(ByteBuffer* buffer, DeserializableControl* control) const { + RecurseGuard RG; control->ensureData(1); int8 code = buffer->getByte(); if (code == -1) @@ -1604,6 +1606,7 @@ FieldConstPtr FieldCreate::deserialize(ByteBuffer* buffer, DeserializableControl namespace detail { struct field_factory { FieldCreatePtr fieldCreate; + epicsThreadPrivate deserDepth; field_factory() :fieldCreate(new FieldCreate()) { registerRefCounter("Field", &Field::num_instances); registerRefCounter("Thread", &Thread::num_instances); @@ -1614,6 +1617,23 @@ struct field_factory { static detail::field_factory* field_factory_s; static epicsThreadOnceId field_factory_once = EPICS_THREAD_ONCE_INIT; +#if defined(__rtems__) || defined(vxWorks) +// lower limit on embedded targets +const unsigned maxDepth = 10; +#else +const unsigned maxDepth = 20; +#endif +RecurseGuard::RecurseGuard() { + size_t current = (size_t)field_factory_s->deserDepth.get(); + if(current>=maxDepth) + throw std::logic_error("FieldCreateFactory recursion depth exceeded"); + field_factory_s->deserDepth.set((unsigned*)(current+1)); +} +RecurseGuard::~RecurseGuard() { + size_t current = (size_t)field_factory_s->deserDepth.get(); + field_factory_s->deserDepth.set((unsigned*)(current-1)); +} + static void field_factory_init(void*) { try { diff --git a/src/factory/PVStructure.cpp b/src/factory/PVStructure.cpp index ff14b816..0a8d91cf 100644 --- a/src/factory/PVStructure.cpp +++ b/src/factory/PVStructure.cpp @@ -18,6 +18,8 @@ #include #include +#include "factoryPvt.h" + using std::tr1::static_pointer_cast; using std::size_t; using std::string; @@ -224,6 +226,7 @@ void PVStructure::serialize(ByteBuffer *pbuffer, void PVStructure::deserialize(ByteBuffer *pbuffer, DeserializableControl *pcontrol) { + RecurseGuard G; size_t fieldsSize = pvFields.size(); for(size_t i = 0; ideserialize(pbuffer, pcontrol); diff --git a/src/factory/PVStructureArray.cpp b/src/factory/PVStructureArray.cpp index 66577b5c..45fdf180 100644 --- a/src/factory/PVStructureArray.cpp +++ b/src/factory/PVStructureArray.cpp @@ -16,6 +16,8 @@ #include #include +#include "factoryPvt.h" + using std::tr1::static_pointer_cast; using std::size_t; @@ -158,6 +160,7 @@ void PVStructureArray::serialize(ByteBuffer *pbuffer, void PVStructureArray::deserialize(ByteBuffer *pbuffer, DeserializableControl *pcontrol) { + RecurseGuard G; svector data(reuse()); size_t size = this->getArray()->getArraySizeType() == Array::fixed ? diff --git a/src/factory/PVUnion.cpp b/src/factory/PVUnion.cpp index a52687b2..8d917d4d 100644 --- a/src/factory/PVUnion.cpp +++ b/src/factory/PVUnion.cpp @@ -18,6 +18,8 @@ #include #include +#include "factoryPvt.h" + using std::tr1::static_pointer_cast; using std::size_t; using std::string; @@ -143,6 +145,7 @@ void PVUnion::serialize(ByteBuffer *pbuffer, SerializableControl *pflusher) cons void PVUnion::deserialize(ByteBuffer *pbuffer, DeserializableControl *pcontrol) { + RecurseGuard G; if (variant) { FieldConstPtr field = pcontrol->cachedDeserialize(pbuffer); diff --git a/src/factory/PVUnionArray.cpp b/src/factory/PVUnionArray.cpp index 151bc5b0..1b4240bd 100644 --- a/src/factory/PVUnionArray.cpp +++ b/src/factory/PVUnionArray.cpp @@ -16,6 +16,8 @@ #include #include +#include "factoryPvt.h" + using std::tr1::static_pointer_cast; using std::size_t; @@ -157,6 +159,7 @@ void PVUnionArray::serialize(ByteBuffer *pbuffer, void PVUnionArray::deserialize(ByteBuffer *pbuffer, DeserializableControl *pcontrol) { + RecurseGuard G; svector data(reuse()); size_t size = this->getArray()->getArraySizeType() == Array::fixed ? diff --git a/src/factory/factoryPvt.h b/src/factory/factoryPvt.h new file mode 100644 index 00000000..baa08de6 --- /dev/null +++ b/src/factory/factoryPvt.h @@ -0,0 +1,14 @@ +#ifndef FACTORYPVT_H +#define FACTORYPVT_H + +namespace epics { namespace pvData { + +// use to bound recursion in FieldCreate and PVDataCreator singletons +struct RecurseGuard { + RecurseGuard(); + ~RecurseGuard(); +}; + +}} // namespace epics::pvData + +#endif // FACTORYPVT_H diff --git a/testApp/misc/testSerialization.cpp b/testApp/misc/testSerialization.cpp index 617586a7..97bf244e 100644 --- a/testApp/misc/testSerialization.cpp +++ b/testApp/misc/testSerialization.cpp @@ -874,13 +874,35 @@ void testMalformed() // a union array where ... CASE("\x89\x83\x2b\x01\x01"); #undef CASE + { + std::vector inp(500u); + for(size_t i=0; i inp(500u); + for(size_t i=0; i