Fix #3546: re-enable FuzzCrashAssets and harden BinaryDecoder#3884
Merged
Conversation
…dens matrix dimensions Issue OPCFoundation#3546 reported that the libfuzz CI pipeline (which feeds a zip of corpus inputs sequentially) caught a DataTypeNode/VariableNode round-trip failure that the local NUnit `EncoderTests` runner did not. The local miss had TWO root causes: 1. `Fuzz.Tests/FuzzTargetTestsBase.FuzzCrashAssets` silently swallowed every exception — counter was incremented but never asserted. So a residual `Assets/crash-*` could sit in the tree without breaking `dotnet test`. 2. The single crash asset on disk reproduces an `ArgumentException` from `MatrixOf<T>(values, dimensions)` (deliberate security validation for overflow / negative / length-mismatch dimensions) escaping `BinaryDecoder.ReadVariantValue` and `ReadEncodeableMatrix<T>`. The fuzz target only swallows `ServiceResultException(BadDecodingError / BadEncodingLimitsExceeded)`, so the unhandled `ArgumentException` surfaced as a "crash" in the pipeline. Both are fixed here. ### Production fix `Stack/Opc.Ua.Types/Encoders/BinaryDecoder.cs`: * Wrap the variant-matrix construction switch in `ReadVariantValue` (covers all 24 BuiltInType cases) in a try/catch that converts `ArgumentException` from `MatrixOf<T>` validation into a `ServiceResultException(BadDecodingError)`. Attacker-controlled wire dimensions are now rejected through the standard decoder channel. * Same treatment for both `ReadEncodeableMatrix<T>` overloads which call `ArrayOf<T>.ToMatrix(dimensions)`. The fix preserves the deliberate `MatrixOf<T>` ctor validation (the security hardening at `MatrixOf.cs:178-217`); it only routes that rejection through the same channel as the rest of the decoder. ### Regression coverage `Tests/Opc.Ua.Types.Tests/Encoders/BinaryDecoderTests.cs`: * `ReadMatrixWithOverflowingDimensionsThrowsBadDecodingError` * `ReadMatrixWithNegativeDimensionThrowsBadDecodingError` * `ReadMatrixWithLengthMismatchThrowsBadDecodingError` `Fuzzing/Opc.Ua.Encoders.Fuzz.Tests/Issue3546RoundTripTests.cs`: * 8 synthetic round-trip tests exercising populated `DataTypeNode` and `VariableNode` through `FuzzableCode.FuzzBinaryEncoderIndempotentCore` (the exact 3rd-gen IsEqual gate that originally failed in CI). All pass, confirming that the modern `Equals` overhaul (`ArrayOf<T>`, `ExtensionObject.Equals`, IEquatable on `ReferenceNode` / `RolePermissionType`) has implicitly resolved the IsEqual non-determinism that OPCFoundation#3546 was filed for. ### Fuzz harness `Fuzzing/Common/Fuzz.Tests/FuzzTargetTestsBase.cs`: * `FuzzCrashAssets` now collects every per-asset failure and finishes with `Assert.That(failures, Is.Empty, ...)`. CI breaks loud when a residual crash asset reproduces — the exact gap that originally let OPCFoundation#3546 slip past `dotnet test`. `.github/agents/fuzz-tester.agent.md`: rewrote the "does not currently Assert" note to reflect the new assertive behaviour and link the gap closure to OPCFoundation#3546. ### Permanent corpus seeds `Fuzzing/Opc.Ua.Encoders.Fuzz.Tools/Encoders.Testcases.cs`: added `DataTypeNodeMessage` and `VariableNodeMessage` message-encoder methods that populate the fields originally implicated (non-empty References, RolePermissions, DataTypeDefinition with an `ExtensionObject` body for `DataTypeNode`; Value `Variant` carrying an `ExtensionObject`, non-empty ArrayDimensions/References/RolePermissions for `VariableNode`). `Fuzzing/Opc.Ua.Encoders.Fuzz.Corpus/Testcases.{Binary,Json,Xml}/`: 6 new seed files (`datatypenodemessage.{bin,json,xml}` and `variablenodemessage.{bin,json,xml}`) generated by the tool. These flow automatically into `FuzzGoodTestcases` so the round-trip is exercised through every fuzz target on every test run. ### Verification (net10.0 + net48) * Encoders.Fuzz.Tests net10.0: 4625 / 0 (baseline 4131 + 6 new seeds * ~81 targets effectively across the matrix + 8 synthetic + 3 other new tests) * Encoders.Fuzz.Tests net48: 4625 / 0 * Types.Tests (BinaryDecoder/Matrix/Variant/EncodeableMatrixRoundTrip subset) net10.0: 985 / 0 (baseline 982 + 3 new regression tests). The single pre-existing `Assets/crash-0483107a...` no longer escapes any fuzz target — the BinaryDecoder fix above turns it into a clean parser-rejected input.
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (69.81%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## master #3884 +/- ##
=======================================
Coverage 72.75% 72.75%
=======================================
Files 913 913
Lines 148286 148305 +19
Branches 25663 25663
=======================================
+ Hits 107879 107901 +22
- Misses 31053 31055 +2
+ Partials 9354 9349 -5
🚀 New features to boost your workflow:
|
marcschier
marked this pull request as ready for review
June 15, 2026 09:51
cristipogacean
approved these changes
Jun 15, 2026
marcschier
added a commit
that referenced
this pull request
Jun 16, 2026
…aryDecoder hardening) Merged two upstream commits: - d8a95b4 dotnet format sweep (whitespace + IDE + RCS) (#3879) - 1c312e5 Fix #3546: re-enable FuzzCrashAssets and harden BinaryDecoder (#3884) Conflict resolution: - CapturingMessageSocket.cs + CapturingMessageSocketFactoryTests.cs + CapturingMessageSocketTests.cs: master's format sweep modified these files that this branch DELETED (the IMessageSocket abstraction was replaced with CapturingByteTransport / IUaSCByteTransport). Kept them deleted. - PcapServiceCollectionExtensions.cs: kept HEAD's ITransportBindingRegistry + ITransportBindingConfigurator wiring over master's removed TransportBindings.Channels static path (2 hunks: xmldoc + the AddTransportBindingRegistry/configurator install). - ClientChannelManager.cs: kept HEAD's DefaultTransportBindingRegistry xmldoc over master's stale TransportBindings.Channels reference. - PcapOptionsDiagnosticsToolsGateTests.cs + PcapServiceCollectionExtensionsTests.cs: dropped master's SetUp/TearDown that snapshot/restore the removed static TransportBindings.Channels; kept HEAD's per-IServiceProvider registry assertions. Post-merge format fixes (master's sweep promoted RCS rules to error and this branch's newer files were not part of that sweep): - HttpsTransportChannel.cs: RCS0009 blank line before the MediaType property doc comment. - SharedKestrelHostTests.cs: RCS1078 use string.Empty instead of empty literal. Verified: dotnet build UA.slnx -c Debug -p:CustomTestTarget=net10.0 clean; 386/386 Opc.Ua.Bindings.Pcap.Tests pass (incl. the MaxBytesCapStopsAcceptingFramesAfterLimit test after a clean rebuild).
marcschier
added a commit
to marcschier/UA-.NETStandard
that referenced
this pull request
Jul 9, 2026
Brings in 4 upstream commits: OPCFoundation#3879 (dotnet format sweep — whitespace + IDE + RCS), OPCFoundation#3884 (re-enable FuzzCrashAssets + harden BinaryDecoder), OPCFoundation#3846 (port session activation race fix from master378), and OPCFoundation#3745 (split MigrationGuide.md, add What's New in 2.0, rewrite Profiles.md). No textual merge conflicts. The format sweep (OPCFoundation#3879) tightened the repo-root Roslynator analyzer configuration, which newly flagged 27 pre-existing UaLens style sites across 14 files (RCS1078 use string.Empty, RCS0031 enum member on own line, RCS1089 use ++ operator, RCS1006/RCS1061 merge if/else, RCS1031 remove unnecessary braces, RCS1051 remove parentheses, RCS0061 remove blank line between switch sections). Auto-fixed via `dotnet format analyzers` (matching how upstream applied the sweep) plus one manual RCS0061 fix in HistorianPlugin that surfaced after the brace removal. UaLens build clean: 0 errors. The 16 remaining warnings are upstream NuGet TFM notices (Microsoft.Extensions.* 10.6.0 not supporting net48/net472 via Opc.Ua.Gds.Client.Common), unrelated to UaLens. Co-authored-by: Copilot <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Two coordinated fixes that close issue #3546 and remove the CI gap that allowed the regression to slip past
dotnet test:BinaryDecoderhardens matrix-dimension handlingStack/Opc.Ua.Types/Encoders/BinaryDecoder.cs— wraps the matrix-construction switch inReadVariantValue(all 24 BuiltInType cases) and bothReadEncodeableMatrix<T>overloads in a try/catch that converts the deliberateArgumentExceptionthrown byMatrixOf<T>(values, dimensions)validation (overflow / negative / length-mismatch) into aServiceResultException(BadDecodingError). Attacker-controlled wire dimensions now flow through the standard decoder rejection channel instead of escaping as an unhandled exception. TheMatrixOf<T>ctor validation itself (security hardening atMatrixOf.cs:178-217) is unchanged.FuzzCrashAssetsis now assertiveFuzzing/Common/Fuzz.Tests/FuzzTargetTestsBase.cs— replaces silent log-and-count withAssert.That(failures, Is.Empty, …listing failed assets). CI now breaks loud the moment a residualAssets/crash-*reproduces on any target..github/agents/fuzz-tester.agent.mddoc note rewritten to match.The originally reported "DataTypeNode / VariableNode
IsEqualnon-determinism" symptom turned out to already be implicitly resolved by the modernEqualsoverhaul (ArrayOf<T>value-equality,ExtensionObject.Equalsdeep-compare,IEquatable<T>onReferenceNode/RolePermissionType). The 8 new synthetic round-trip tests + 6 permanent corpus seeds confirm this end-to-end.Regression coverage
Tests/Opc.Ua.Types.Tests/Encoders/BinaryDecoderTests.cs— 3 new tests lock in theBadDecodingErrorcontract for malformed matrix dimensions (overflow, negative, length-mismatch).Fuzzing/Opc.Ua.Encoders.Fuzz.Tests/Issue3546RoundTripTests.cs— 8 synthetic round-trip tests for populatedDataTypeNodeandVariableNode, exercising the implicated fields (non-empty References, RolePermissions,DataTypeDefinitionExtensionObject body,ValueVariant carrying ExtensionObject,ArrayDimensionsempty / null / non-empty).Fuzzing/Opc.Ua.Encoders.Fuzz.Tools/Encoders.Testcases.cs+Fuzzing/Opc.Ua.Encoders.Fuzz.Corpus/Testcases.{Binary,Json,Xml}/—DataTypeNodeMessage/VariableNodeMessageseed generators plus 6 generated corpus files. These feedFuzzGoodTestcasesautomatically on every test run.Verification
Opc.Ua.Encoders.Fuzz.TestsOpc.Ua.Encoders.Fuzz.TestsOpc.Ua.Types.Tests(BinaryDecoder / Matrix / Variant / EncodeableMatrixRoundTrip subset)The single pre-existing
Assets/crash-0483107a22caf892cff4f795c96ab14c581a8f55no longer escapes any fuzz target — the matrix-dimensions fix turns it into a clean parser-rejected input.Related Issues
Checklist
Opc.Ua.Encoders.Fuzz.Testson net10.0 + net48, targetedOpc.Ua.Types.Testssubset on net10.0); full UA.slnx still pending CI.