From 80c16f32cdda828a3091f0c5e366f03c4e7872ba Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 27 Oct 2026 07:21:44 +0100 Subject: [PATCH] Reject invalid UADP discovery types --- docs/DataChannels.md | 7 ++++-- .../Encoding/Uadp/UadpDiscoveryCoder.cs | 10 ++++++++ .../Uadp/UadpDecoderMalformedTests.cs | 24 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/DataChannels.md b/docs/DataChannels.md index 3786334c95..831f04f30e 100644 --- a/docs/DataChannels.md +++ b/docs/DataChannels.md @@ -737,8 +737,11 @@ or empty test run is not native acceptance. discovery requests or PublisherEndpoints responses without writer IDs. Callers can register explicit PublisherId, NetworkMessageType and optional discovery-subtype selectors for those messages. Subtype selectors apply only - to discovery NetworkMessageTypes 1 and 2; request subtype values are 1–6 and - response values are 1–5. A secured header hides the subtype; + to discovery NetworkMessageTypes 1 and 2. Request subtype values 1–6 identify + PublisherEndpoints, DataSetMetaData, DataSetWriterConfiguration, + ApplicationInformation, PubSubConnection and Probe, in that order. Response + subtype values are 1–5; Probe has no response subtype. A secured header hides + the subtype; when a matching subtype-specific association exists, the context rejects that message instead of falling back to a broader route. The [UASC discovery routing tests](../tests/Opc.Ua.Core.DataChannels.Tests/Runtime/FlowDeliveryTests.cs) diff --git a/src/Opc.Ua.PubSub/Encoding/Uadp/UadpDiscoveryCoder.cs b/src/Opc.Ua.PubSub/Encoding/Uadp/UadpDiscoveryCoder.cs index caa0a0c986..b57070e466 100644 --- a/src/Opc.Ua.PubSub/Encoding/Uadp/UadpDiscoveryCoder.cs +++ b/src/Opc.Ua.PubSub/Encoding/Uadp/UadpDiscoveryCoder.cs @@ -278,6 +278,11 @@ namespace Opc.Ua.PubSub.Encoding.Uadp { return null; } + if (typeByte < (byte)UadpDiscoveryType.PublisherEndpoints || + typeByte > (byte)UadpDiscoveryType.Probe) + { + return null; + } ushort[] ids; UadpDiscoveryProbeFilter? filter = null; try @@ -330,6 +335,11 @@ namespace Opc.Ua.PubSub.Encoding.Uadp { return null; } + if (typeByte < (byte)UadpDiscoveryType.PublisherEndpoints || + typeByte > (byte)UadpDiscoveryType.PubSubConnection) + { + return null; + } if (!reader.TryReadUInt16Le(out ushort sequenceNumber)) { return null; diff --git a/tests/Opc.Ua.PubSub.Tests/Encoding/Uadp/UadpDecoderMalformedTests.cs b/tests/Opc.Ua.PubSub.Tests/Encoding/Uadp/UadpDecoderMalformedTests.cs index 26dc2844de..6612a13ea9 100644 --- a/tests/Opc.Ua.PubSub.Tests/Encoding/Uadp/UadpDecoderMalformedTests.cs +++ b/tests/Opc.Ua.PubSub.Tests/Encoding/Uadp/UadpDecoderMalformedTests.cs @@ -392,6 +392,18 @@ namespace Opc.Ua.PubSub.Tests.Encoding.Uadp Assert.That(decoded, Is.Null); } + [TestCase(0)] + [TestCase(7)] + [TestCase(byte.MaxValue)] + public void DiscoveryRequestRejectsUnsupportedSubtype(byte subtype) + { + byte[] frame = [0x91, 0x80, 0x04, 0x00, subtype, 0x00, 0x00, 0x00, 0x00]; + + PubSubNetworkMessage? decoded = UadpDecoder.Decode(frame, UadpTestUtilities.NewContext()); + + Assert.That(decoded, Is.Null); + } + [Test] public void DiscoveryResponseRejectsEndpointCountBeyondRemainingBytes() { @@ -403,6 +415,18 @@ namespace Opc.Ua.PubSub.Tests.Encoding.Uadp Assert.That(decoded, Is.Null); } + [TestCase(0)] + [TestCase(6)] + [TestCase(byte.MaxValue)] + public void DiscoveryResponseRejectsUnsupportedSubtype(byte subtype) + { + byte[] frame = [0x91, 0x80, 0x08, 0x00, subtype, 0x00, 0x00]; + + PubSubNetworkMessage? decoded = UadpDecoder.Decode(frame, UadpTestUtilities.NewContext()); + + Assert.That(decoded, Is.Null); + } + [Test] public void DiscoveryResponseRejectsWriterIdCountBeyondRemainingBytes() {