mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-18 14:43:08 +08:00
594eb7a245
Minimal backports to the 1.5.378 line of three findings from the codec audit behind #4599 / #4602 (B1-1, X1-1, P1-1). All three can be reached from untrusted input. The changes are kept small, and the performance of legitimate messages is unchanged. ## Changes **B1-1: nested array preallocation (`BinaryDecoder`)** Variant, DataValue, ExtensionObject and encodeable arrays were allocated at the full length prefix before any element was read. Arrays nested in the first element of their parent all see the same remaining message, so a chain of them multiplied the allocation: about 1 KB of input in `RequestHeader.AdditionalHeader` preallocated about 200 MB before authentication. - These arrays now use the #4602 budget. An array is allocated at full length only while all such arrays still being read take at most 8 bytes per remaining message byte. Otherwise it starts at 16 KB and grows as elements are read. - The reservation is released in a `finally`. - Leaf arrays (numbers, strings, NodeIds, …) are unchanged and stay bounded by MaxArrayLength. **X1-1: XML element depth (`XmlDecoder`, `BinaryDecoder`, `JsonDecoder`)** The depth of XmlElement values and XML ExtensionObject bodies was unbounded, and the `InnerXml` setter and `CloneNode(true)` recurse once per level. A known-type XML body of about 200 KB inside a binary message terminated the process with a stack overflow. - Before any network XML is loaded into a DOM, it is now checked with a streaming reader. XML nested deeper than `MaxEncodingNestingLevels` (default 200, the same limit master uses) is rejected with `BadEncodingLimitsExceeded`. - Master's node-by-node copier is not ported. **P1-1: inbound DataSetMetaData (`UaPubSubConnection`)** Received metadata is not authenticated, yet it replaced the metadata of every reader with a matching DataSetWriterId, whichever publisher sent it. - It now only updates readers whose PublisherId filter accepts the sender, using the same rule as data messages. A reader without a PublisherId still accepts any publisher. - Master's registry cap is not needed, because 378 has no registry. ## Verification (net10.0 unless noted) | Check | Result | |---|---| | Nested Variant-array chain, 995 bytes | 199.2 MB → 3.3 MB allocated | | Known-type XML body 30 000 levels deep inside binary | process crash → `BadEncodingLimitsExceeded` | | `Opc.Ua.Types.Tests` (incl. new `DecoderNestingLimitTests`) | 327 / 327; new tests also 9 / 9 on net48 | | `Opc.Ua.Encoders.Fuzz.Tests` (crash/timeout/slow corpus) | 150 / 150 | | `Opc.Ua.Core.Tests` (Encoders + Types) | 6 327 passed, 0 failed | | `Opc.Ua.PubSub.Tests` (without broker-dependent MQTT/UDP) + new `MetaDataPublisherFilterTests` | 617 + 4 passed, 13 skipped | | Types / Core / PubSub build, all TFMs | 0 warnings | **Performance vs. db39629d2:** - Test cases: decoding `Variant[100000]`, `Variant(Variant[])`, `DataValue[10000]`, `ReadValueId[20000]` and `ExtensionObject[20000]`. - Allocations are byte-identical to the baseline. - Median times are within run-to-run noise over two interleaved rounds. - The only new cost is one extra streaming parse for XmlElement values. ## Notes for review - If a deployment raises `MaxEncodingNestingLevels` well above the default, the allowed XML depth rises with it. Master behaves the same way. - No public API changes. The XML depth helper is internal to `Opc.Ua.Types`, and `JsonDecoder` in `Opc.Ua.Core` has a private copy. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>