Skip to content

Add protection from some network issues - #107

Draft
anjohnson wants to merge 6 commits into
epics-base:masterfrom
anjohnson:add-protection
Draft

Add protection from some network issues#107
anjohnson wants to merge 6 commits into
epics-base:masterfrom
anjohnson:add-protection

Conversation

@anjohnson

@anjohnson anjohnson commented Jul 10, 2026

Copy link
Copy Markdown
Member

Malicious traffic from the network wasn't being checked properly. These changes don't prevent sending huge arrays or string values, but they do stop issues related to overflows caused by negative sizes, which PVA specifies to be positive int32 values.

  1. Limit allocation in BitSet::deserialize() to the size actually needed. Support deserializing a NULL value. Includes new tests.

2, 3. The PVStructureArray, PVUnionArray and PVValueArray<string> deserialize() methods now allocate their storage as data arrives, and reject negative sizes. Includes new basic and malicious tests.

  1. Add a maximum string length to SerializeHelper::deserializeString(), with a default of INT32_MAX.

  2. Protect Status::::deserialize() from a bad status.

  3. Protect PVUnion::deserialize() from a bad selector value.

@codacy-production

codacy-production Bot commented Jul 10, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · -2 duplication

Metric Results
Complexity 0
Duplication -2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@AppVeyorBot

Copy link
Copy Markdown

Comment thread src/factory/PVStructureArray.cpp Outdated
@AppVeyorBot

Copy link
Copy Markdown

@anjohnson

Copy link
Copy Markdown
Member Author

How to handle the POD array template PVValueArray<T>::deserialize(), which I haven't done yet? There is a short-cut there: when the endianness matches all the elements can be copied straight from the message buffer, but that could be a huge read.

I'm currently looking at the RTEMS-4.9 test failure.

@anjohnson

Copy link
Copy Markdown
Member Author

The pvData / 7.0 Ub-22 gcc + RT-4.9 pc386 build is failing in the new testSerialization tests, the .tap output starts and ends like this:

1..290
# Testing introspection serialization...
ok  1 - factory.get()!=NULL
…
ok 246 - grow/reuse round trip, 129 elements
ok 247 - grow/reuse round trip, 129 elements
ok 248 - grow/reuse round trip, 1000 elements
rtems_gxx_mutex_init
fatal error, exiting

Those symptoms look like a stack overflow or similar issue, 1000 elements is the largest the new tests request. The RT-4.10 and 5.1 builds don't fail; is this worth chasing?

@AppVeyorBot

Copy link
Copy Markdown

@anjohnson anjohnson left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/code-review comments from Claude; some fixes to come, but not the generic PVValueArray<T>::deserialize() fix which we haven't decided on yet.

Comment thread src/misc/bitSet.cpp Outdated
Comment thread src/factory/PVStructureArray.cpp
Comment on lines +173 to +174
data.resize(0);
data.reserve(size < 64 ? size : 64);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Sonnet 5: The old per-element reuse optimization (reuse still-unique PVStructure/PVUnion objects across deserialize calls) is silently discarded.
The new reserve(min(size,64)) + push_back growth pattern interacts badly with shared_vector's fixed +1024-element growth increment above 1024 items, causing O(n²) copy work for legitimately large arrays.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shared_vector<…>::push_back() method only grows geometrically up to 1024 elements, thereafter it adds 1024 elements every time it expands. An areaDetector NTNDArray has an epics:nt/NTAttribute:1.0[] attribute array field which would hit the O(n²) allocation, but hopefully only the first time the data is published.

Subsequent monitor updates should re-use the array storage where the attribute list doesn't change, but some experiments may continue to add attributes over time, and those would be the ones I worry about.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subsequent monitor updates should re-use the array storage ...

A reuse() is attempted because it is low cost. However, I think it is unlikely to succeed in the most important cases (AD). It would be unlikely in the case of a QSRV1 group mapping as a reference to the most recent value is kept.

I worry that this attempt to "harden" against a malicious peer will unduly harm performance on all others. So I am going to hold off on this part until the core developer group agrees on a policy on regarding DoS mitigations which negatively impact performance.

Comment thread src/factory/PVDataCreateFactory.cpp
@AppVeyorBot

Copy link
Copy Markdown

Move bounds check to before the allocation.
Treat NULL bitset values as empty to avoid overflow.

Add tests for excessive allocation, and Null values.
Ensure container types PVStructureArray and PVUnionArray can't be
used to allocate huge arrays, unless they really are that big.
New containers start small and grow as as elements are deserialized
from the input stream.
@anjohnson
anjohnson force-pushed the add-protection branch 2 times, most recently from df17c76 to a8e4961 Compare July 27, 2026 19:17
Ensure PVValueArray<string> containers can't allocate huge arrays
unless the client provides string values for all the elements.
Start containers small and grow them as elements are deserialized
from the input stream.

Adds tests for all the array deserialization hardening.
Limit array growth tests on RTEMS 4.x.
@anjohnson
anjohnson marked this pull request as ready for review July 27, 2026 20:19
@AppVeyorBot

Copy link
Copy Markdown

@AppVeyorBot

Copy link
Copy Markdown

@AppVeyorBot

Copy link
Copy Markdown

@AppVeyorBot

Copy link
Copy Markdown

Defaults to INT32_MAX if not provided, but several places in
pvAccess can set limits, e.g. MAX_CHANNEL_NAME_LENGTH, and
this is the best way to harden them against huge values.

Document that SerializeHelper::readSize() returns -1 for NULL.
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · -2 duplication

Metric Results
Complexity 0
Duplication -2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@AppVeyorBot

Copy link
Copy Markdown

Build pvDataCPP 1.0.92 failed (commit 1f6f3b31dc by @anjohnson)

@AppVeyorBot

Copy link
Copy Markdown

@mdavidsaver mdavidsaver left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have picked off the uncontentious parts of this PR as 5012ff9, 2c5854a, e7c69cb, and 89d2702.

Comment thread src/factory/PVUnion.cpp
selector = index;
value.reset();
}
else if (index < 0 || size_t(index) >= unionPtr->getFields().size())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is redundant to the later getField(selector);, which bounds checks its argument. Still, an explicit check here gives a better error message.

*/
static std::string deserializeString(ByteBuffer* buffer,
DeserializableControl* control);
DeserializableControl* control, std::size_t max = INT32_MAX);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No objection to the added argument. I have a small concern about whether the INT32_MAX macro will be defined on all targets. Strictly speaking, the C++ way is std::numeric_limits. However, since this is a fixed value, I will just change it to 0x7fffffff.

Comment thread src/misc/status.cpp
m_statusType = STATUSTYPE_FATAL;
m_message = "deserialized type code was invalid";
m_stackDump.clear();
THROW_BASE_EXCEPTION("invalid Status type code");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I do not see much chance that we will want to add codes in future, it seems unnecessary to close the door on this possibility now. I will instead treat all currently unknown codes as FATAL.

Comment on lines +173 to +174
data.resize(0);
data.reserve(size < 64 ? size : 64);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subsequent monitor updates should re-use the array storage ...

A reuse() is attempted because it is low cost. However, I think it is unlikely to succeed in the most important cases (AD). It would be unlikely in the case of a QSRV1 group mapping as a reference to the most recent value is kept.

I worry that this attempt to "harden" against a malicious peer will unduly harm performance on all others. So I am going to hold off on this part until the core developer group agrees on a policy on regarding DoS mitigations which negatively impact performance.

@anjohnson

Copy link
Copy Markdown
Member Author

Core: Some changes have been merged by cherry-pick. This PR needs further thought, the performance-affecting changes might not be wanted.

@anjohnson
anjohnson marked this pull request as draft August 19, 2026 14:17
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.

4 participants