Skip to content

[master378] Bound nested array preallocation, XML depth and inbound PubSub metadata - #4603

Merged
romanett merged 5 commits into
master378from
romanett/security-audit-master378-4932df
Oct 1, 2026
Merged

romanett merged 5 commits into
master378from
romanett/security-audit-master378-4932df

Conversation

@romanett

Copy link
Copy Markdown
Contributor

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 Restore BinaryDecoder array performance after the length-prefix hardening (#4598) #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. db39629:

  • 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

@romanett
romanett marked this pull request as ready for review September 30, 2026 13:40
@romanett
romanett requested a review from mrsuciu September 30, 2026 13:41
@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.07843% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.10%. Comparing base (db39629) to head (25e0f47).

Files with missing lines Patch % Lines
Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs 81.81% 1 Missing and 1 partial ⚠️
Stack/Opc.Ua.Types/Encoders/BinaryDecoder.cs 98.57% 0 Missing and 1 partial ⚠️
Stack/Opc.Ua.Types/Encoders/XmlElementDepth.cs 93.75% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##           master378    #4603      +/-   ##
=============================================
+ Coverage      61.99%   62.10%   +0.11%     
=============================================
  Files            381      382       +1     
  Lines          79445    79514      +69     
  Branches       13918    13927       +9     
=============================================
+ Hits           49249    49385     +136     
+ Misses         25771    25696      -75     
- Partials        4425     4433       +8     
Files with missing lines Coverage Δ
Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs 73.48% <100.00%> (+0.35%) ⬆️
Stack/Opc.Ua.Types/Encoders/XmlDecoder.cs 66.95% <100.00%> (+0.07%) ⬆️
Stack/Opc.Ua.Types/Encoders/BinaryDecoder.cs 71.34% <98.57%> (+0.98%) ⬆️
Stack/Opc.Ua.Types/Encoders/XmlElementDepth.cs 93.75% <93.75%> (ø)
Libraries/Opc.Ua.PubSub/UaPubSubConnection.cs 50.23% <81.81%> (+22.46%) ⬆️

... and 15 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

romanett and others added 2 commits September 30, 2026 15:42
…ubSub metadata

Backports minimal fixes for three codec audit findings (B1-1, X1-1, P1-1)
reachable from untrusted input on the 1.5.378 line.

- B1-1: BinaryDecoder allocated Variant, DataValue, ExtensionObject and
  encodeable arrays at the length prefix before reading the elements, and
  every level of a chain of such arrays nested in the first element of its
  parent saw the same remaining bytes, so ~1 KB of unauthenticated input
  (RequestHeader.AdditionalHeader) preallocated ~200 MB. These arrays are
  now allocated at full length only while all of them still being read take
  at most 8 bytes per remaining message byte; otherwise they start at 16 KB
  and grow as elements are read (the budget of #4602). Leaf arrays are
  unchanged and stay bounded by MaxArrayLength.
- X1-1: XmlElement values and XML ExtensionObject bodies had unbounded
  element depth, and the XmlElement.InnerXml setter / XmlNode.CloneNode(true)
  recurse once per level, so a ~200 KB XML body of a known type inside a
  binary message overflowed the stack and terminated the process. The binary,
  XML and JSON decoders now reject XML nested deeper than
  MaxEncodingNestingLevels with BadEncodingLimitsExceeded, checked with a
  streaming reader before the XML is loaded into a DOM.
- P1-1: received DataSetMetaData (not authenticated) replaced the metadata of
  every reader with a matching DataSetWriterId, whatever publisher sent it.
  It now only updates readers whose PublisherId filter accepts the sender,
  the same filter the data messages use (a reader without PublisherId still
  accepts any publisher).

Allocation of legitimate large arrays is unchanged and decode time is within
noise (Variant[100000], DataValue[10000], ReadValueId[20000],
ExtensionObject[20000] vs. db39629).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Id filter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
{
case Encoding.JsonNetworkMessage jsonNetworkMessage:
return jsonNetworkMessage.PublisherId != null &&
jsonNetworkMessage.PublisherId.Equals(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use Use Utils.IsEqual(jsonNetworkMessage.PublisherId, readerPublisherId) as in

!Utils.IsEqual(
reader.DataSetMetaData.ConfigurationVersion,
networkMessage.DataSetMetaData.ConfigurationVersion)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Updated the JSON PublisherId comparison to use Utils.IsEqual while preserving the existing string normalization; commit 9bbd25f.

StringComparison.Ordinal);
case Encoding.UadpNetworkMessage uadpNetworkMessage:
return uadpNetworkMessage.PublisherId != null &&
uadpNetworkMessage.PublisherId.Equals(readerPublisherId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same as above

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Updated the UADP PublisherId comparison to use Utils.IsEqual; commit 9bbd25f.

/// The streaming reader used for the check does not recurse.
/// </summary>
/// <exception cref="ServiceResultException"></exception>
private void CheckXmlElementDepth(string xml)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This method is almost identic with the one implemented in XmlDecoder.cs except missing some checks. Why not use that one also in this file?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The JSON decoder now calls XmlDecoder.CheckXmlElementDepth with document conformance; Opc.Ua.Core was granted internal access so the shared checks stay internal rather than duplicating them or adding public API. Commit 9bbd25f.

@romanett

romanett commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot adress review feedback

Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ romanett
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

@romanett

romanett commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix failing tests

Copilot AI and others added 2 commits October 1, 2026 09:06
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>
Co-authored-by: romanett <7413710+romanett@users.noreply.github.com>

Copilot AI commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

@copilot fix failing tests

Fixed the build failure: the friend-assembly declaration exposed the conflicting internal Namespaces type, causing CS0436 errors. The XML depth checker is now shared as an internal source file compiled into both assemblies. Core builds with zero warnings/errors, and the focused JSON, Types, and PubSub tests pass. Commits: 5e2fb5a and 25e0f47.

}

var values = new VariantCollection(length);
int capacity = ReserveArrayCapacity(length, s_variantElementSize, out long reserved);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why only these Read[Variant]Array reserve space, why not also ReadStringArray and ReadNodeIdArray and other arrays ? can't the length be "spoofed" also and allocate more than necessary ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Only Variant Array can Nest which makes a real amplification possible

@romanett
romanett enabled auto-merge (squash) October 1, 2026 12:59
@romanett
romanett merged commit 594eb7a into master378 Oct 1, 2026
92 of 93 checks passed
GoetzGoerisch pushed a commit to umati/connect that referenced this pull request Oct 5, 2026
…5.378.182 (#52)

This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [OPCFoundation.NetStandard.Opc.Ua](https://github.com/OPCFoundation/UA-.NETStandard) | `1.5.378.176` → `1.5.378.182` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/OPCFoundation.NetStandard.Opc.Ua/1.5.378.182?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/OPCFoundation.NetStandard.Opc.Ua/1.5.378.176/1.5.378.182?slim=true) |

---

### Release Notes

<details>
<summary>OPCFoundation/UA-.NETStandard (OPCFoundation.NetStandard.Opc.Ua)</summary>

### [`v1.5.378.182`](https://github.com/OPCFoundation/UA-.NETStandard/releases/tag/1.5.378.182): OPC UA 1.05 Maintenance Update

[Compare Source](OPCFoundation/UA-.NETStandard@1.5.378.176...1.5.378.182)

Maintenance Release for fixing bugs found on the main378 development branch.

#### Released packages

[OPCFoundation.NetStandard.Opc.Ua](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Core](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Security.Certificates](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Security.Certificates/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Configuration](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Configuration/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Server](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Client](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Bindings.Https](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Bindings.Https/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.PubSub](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.PubSub/1.5.378.182)

#### What's Changed

- Remove unused fields from ReferenceNodeManager by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;4472](OPCFoundation/UA-.NETStandard#4472)
- Master378 fix incomplete message buffering by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;4550](OPCFoundation/UA-.NETStandard#4550)
- Fix ServerConfiguration URI values for CTT push-management checks by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;4592](OPCFoundation/UA-.NETStandard#4592)
- Bound nested array preallocation, XML depth and inbound PubSub metadata by [@&#8203;romanett](https://github.com/romanett) in [#&#8203;4603](OPCFoundation/UA-.NETStandard#4603)
- Fix queued data delivery after durable subscription transfer by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;4622](OPCFoundation/UA-.NETStandard#4622)
- Fix GDS client deadlock after a failed keep-alive by [@&#8203;romanett](https://github.com/romanett) in [#&#8203;4623](OPCFoundation/UA-.NETStandard#4623)
- merge Master378 to release by [@&#8203;romanett](https://github.com/romanett) in [#&#8203;4628](OPCFoundation/UA-.NETStandard#4628)

**Full Changelog**: <OPCFoundation/UA-.NETStandard@1.5.378.176...1.5.378.182>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzIuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEzMi4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://codeberg.org/umati/connect/pulls/52
Signed-off-by: umati-bot <git-bot@umati.org>
GoetzGoerisch pushed a commit to umati/connect that referenced this pull request Oct 5, 2026
…r to 1.5.378.182 (#53)

This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [OPCFoundation.NetStandard.Opc.Ua.Server](https://github.com/OPCFoundation/UA-.NETStandard) | `1.5.378.176` → `1.5.378.182` | ![age](https://developer.mend.io/api/mc/badges/age/nuget/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.182?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/nuget/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.176/1.5.378.182?slim=true) |

---

### Release Notes

<details>
<summary>OPCFoundation/UA-.NETStandard (OPCFoundation.NetStandard.Opc.Ua.Server)</summary>

### [`v1.5.378.182`](https://github.com/OPCFoundation/UA-.NETStandard/releases/tag/1.5.378.182): OPC UA 1.05 Maintenance Update

[Compare Source](OPCFoundation/UA-.NETStandard@1.5.378.176...1.5.378.182)

Maintenance Release for fixing bugs found on the main378 development branch.

#### Released packages

[OPCFoundation.NetStandard.Opc.Ua](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Core](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Security.Certificates](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Security.Certificates/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Configuration](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Configuration/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Server](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Client](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.Bindings.Https](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Bindings.Https/1.5.378.182)
[OPCFoundation.NetStandard.Opc.Ua.PubSub](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.PubSub/1.5.378.182)

#### What's Changed

- Remove unused fields from ReferenceNodeManager by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;4472](OPCFoundation/UA-.NETStandard#4472)
- Master378 fix incomplete message buffering by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;4550](OPCFoundation/UA-.NETStandard#4550)
- Fix ServerConfiguration URI values for CTT push-management checks by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;4592](OPCFoundation/UA-.NETStandard#4592)
- Bound nested array preallocation, XML depth and inbound PubSub metadata by [@&#8203;romanett](https://github.com/romanett) in [#&#8203;4603](OPCFoundation/UA-.NETStandard#4603)
- Fix queued data delivery after durable subscription transfer by [@&#8203;mrsuciu](https://github.com/mrsuciu) in [#&#8203;4622](OPCFoundation/UA-.NETStandard#4622)
- Fix GDS client deadlock after a failed keep-alive by [@&#8203;romanett](https://github.com/romanett) in [#&#8203;4623](OPCFoundation/UA-.NETStandard#4623)
- merge Master378 to release by [@&#8203;romanett](https://github.com/romanett) in [#&#8203;4628](OPCFoundation/UA-.NETStandard#4628)

**Full Changelog**: <OPCFoundation/UA-.NETStandard@1.5.378.176...1.5.378.182>

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzIuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEzMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

Reviewed-on: https://codeberg.org/umati/connect/pulls/53
Signed-off-by: umati-bot <git-bot@umati.org>
@romanett
romanett deleted the romanett/security-audit-master378-4932df branch October 7, 2026 03:48
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