diff --git a/src/Tmds.DBus.Protocol/ProtocolConstants.cs b/src/Tmds.DBus.Protocol/ProtocolConstants.cs index 651587f..387c07c 100644 --- a/src/Tmds.DBus.Protocol/ProtocolConstants.cs +++ b/src/Tmds.DBus.Protocol/ProtocolConstants.cs @@ -5,6 +5,7 @@ static class ProtocolConstants public const int MaxSignatureLength = 256; public const uint MaxArrayLength = 64 * 1024 * 1024; public const uint MaxMessageLength = 128 * 1024 * 1024; + public const int MaxVariantRecursionDepth = 64; public const int StructAlignment = 8; public const int UInt32Alignment = 4; diff --git a/src/Tmds.DBus.Protocol/Reader.Variant.cs b/src/Tmds.DBus.Protocol/Reader.Variant.cs index 450d88b..18e129d 100644 --- a/src/Tmds.DBus.Protocol/Reader.Variant.cs +++ b/src/Tmds.DBus.Protocol/Reader.Variant.cs @@ -7,26 +7,31 @@ public ref partial struct Reader /// /// The variant value. public VariantValue ReadVariantValue() - => ReadVariantValue(nesting: 0); + => ReadVariantValue(nesting: 0, recursionDepth: 0); - private VariantValue ReadVariantValue(byte nesting) + private VariantValue ReadVariantValue(byte nesting, int recursionDepth) { ReadOnlySpan signature = ReadSignatureAsSpan(); - return ReadVariantValue(signature, nesting); + return ReadVariantValue(signature, nesting, recursionDepth); } - internal VariantValue ReadVariantValue(ReadOnlySpan signature, byte nesting = 0) + internal VariantValue ReadVariantValue(ReadOnlySpan signature, byte nesting = 0, int recursionDepth = 0) { SignatureReader sigReader = new(signature); if (!sigReader.TryRead(out DBusType type, out ReadOnlySpan innerSignature)) { ThrowInvalidSignature($"Invalid variant signature: {ThrowHelper.SignatureToStringNoThrow(signature)}"); } - return ReadTypeAsVariantValue(type, innerSignature, nesting); + return ReadTypeAsVariantValue(type, innerSignature, nesting, recursionDepth); } - private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan innerSignature, byte nesting) + private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan innerSignature, byte nesting, int recursionDepth) { + recursionDepth++; + if (recursionDepth > ProtocolConstants.MaxVariantRecursionDepth) + { + ThrowHelper.ThrowReaderRecursionDepthExceeded(); + } SignatureReader sigReader; switch (type) { @@ -59,7 +64,7 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan in return new VariantValue(_handles, idx, nesting); case DBusType.Variant: nesting += 1; - return ReadVariantValue(nesting); + return ReadVariantValue(nesting, recursionDepth); case DBusType.Array: ReadOnlySpan itemSignature = innerSignature; sigReader = new(innerSignature); @@ -83,10 +88,10 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan in while (HasNext(arrayEnd)) { AlignStruct(); - VariantValue key = ReadTypeAsVariantValue(keyType, keyInnerSignature, nesting: 0); + VariantValue key = ReadTypeAsVariantValue(keyType, keyInnerSignature, nesting: 0, recursionDepth); VariantValue value = valueType == DBusType.Variant - ? ReadVariantValue() // unwrap - : ReadTypeAsVariantValue(valueType, valueInnerSignature, nesting: 0); + ? ReadVariantValue(nesting: 0, recursionDepth) // unwrap + : ReadTypeAsVariantValue(valueType, valueInnerSignature, nesting: 0, recursionDepth); items.Add(new KeyValuePair(key, value)); } ReadOnlySpan valueSignature = itemSignature.Slice(2, itemSignature.Length - 3); @@ -141,8 +146,8 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan in while (HasNext(arrayEnd)) { VariantValue value = type == DBusType.Variant - ? ReadVariantValue() // unwrap - : ReadTypeAsVariantValue(type, innerSignature, nesting: 0); + ? ReadVariantValue(nesting: 0, recursionDepth) // unwrap + : ReadTypeAsVariantValue(type, innerSignature, nesting: 0, recursionDepth); items.Add(value); } return new VariantValue(ToVariantValueType(type, innerSignature), VariantValue.GetSignatureObject(items.Count, itemSignature), items.ToArray(), nesting); @@ -161,11 +166,11 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan in if (type == DBusType.Variant) { variantMask |= (1L << i); - value = ReadVariantValue(); // unwrap + value = ReadVariantValue(nesting: 0, recursionDepth); // unwrap } else { - value = ReadTypeAsVariantValue(type, innerSignature, nesting: 0); + value = ReadTypeAsVariantValue(type, innerSignature, nesting: 0, recursionDepth); } items.Add(value); i++; diff --git a/src/Tmds.DBus.Protocol/ThrowHelper.cs b/src/Tmds.DBus.Protocol/ThrowHelper.cs index 6c8be74..1bbb3e3 100644 --- a/src/Tmds.DBus.Protocol/ThrowHelper.cs +++ b/src/Tmds.DBus.Protocol/ThrowHelper.cs @@ -52,6 +52,12 @@ internal static void ThrowReaderInvalidUTF8() throw new DBusReadException("Invalid UTF-8 sequence."); } + [DoesNotReturn] + internal static void ThrowReaderRecursionDepthExceeded() + { + throw new DBusReadException($"Variant recursion exceeds the allowed ({ProtocolConstants.MaxVariantRecursionDepth})."); + } + [DoesNotReturn] internal static void ThrowReaderNoFileHandle() { diff --git a/test/Tmds.DBus.Protocol.Tests/ReaderTests.cs b/test/Tmds.DBus.Protocol.Tests/ReaderTests.cs index 575782f..236ed07 100644 --- a/test/Tmds.DBus.Protocol.Tests/ReaderTests.cs +++ b/test/Tmds.DBus.Protocol.Tests/ReaderTests.cs @@ -533,6 +533,85 @@ public static IEnumerable ReadVariantValueTestData } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReadVariantValue_VariantRecursionDepthLimit(bool exceedLimit) + { + // Each variant level calls ReadTypeAsVariantValue once, plus one more for the final byte value. + int depth = ProtocolConstants.MaxVariantRecursionDepth - 1 + (exceedLimit ? 2 : 0); + byte[] data = new byte[depth * 3 + 4]; + int offset = 0; + for (int i = 0; i < depth; i++) + { + data[offset++] = 1; // signature length + data[offset++] = (byte)'v'; // variant type + data[offset++] = 0; // null terminator + } + data[offset++] = 1; // signature length + data[offset++] = (byte)'y'; // byte type + data[offset++] = 0; // null terminator + data[offset++] = 42; // byte value + + if (exceedLimit) + { + var exception = Assert.Throws(() => + { + Reader reader = new Reader(isBigEndian: false, new System.Buffers.ReadOnlySequence(data)); + reader.ReadVariantValue(); + }); + Assert.Equal($"Variant recursion exceeds the allowed ({ProtocolConstants.MaxVariantRecursionDepth}).", exception.Message); + } + else + { + Reader reader = new Reader(isBigEndian: false, new System.Buffers.ReadOnlySequence(data)); + reader.ReadVariantValue(); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ReadVariantValue_StructVariantRecursionDepthLimit(bool exceedLimit) + { + // Each (v) level calls ReadTypeAsVariantValue twice (struct + variant), plus one for the final byte. + int depth = exceedLimit + ? ProtocolConstants.MaxVariantRecursionDepth + 1 + : (ProtocolConstants.MaxVariantRecursionDepth - 1) / 2; + List bytes = new(); + for (int i = 0; i < depth; i++) + { + bytes.Add(3); // signature length + bytes.Add((byte)'('); + bytes.Add((byte)'v'); + bytes.Add((byte)')'); + bytes.Add(0); // null terminator + // Pad to 8-byte alignment for struct. + while (bytes.Count % 8 != 0) + bytes.Add(0); + } + // Innermost variant value: a byte. + bytes.Add(1); // signature length + bytes.Add((byte)'y'); // byte type + bytes.Add(0); // null terminator + bytes.Add(42); // byte value + + if (exceedLimit) + { + var exception = Assert.Throws(() => + { + Reader reader = new Reader(isBigEndian: false, new System.Buffers.ReadOnlySequence(bytes.ToArray())); + reader.ReadVariantValue(); + }); + Assert.Equal($"Variant recursion exceeds the allowed ({ProtocolConstants.MaxVariantRecursionDepth}).", exception.Message); + } + else + { + Reader reader = new Reader(isBigEndian: false, new System.Buffers.ReadOnlySequence(bytes.ToArray())); + reader.ReadVariantValue(); + } + } + private void TestRead(T expected, ReadFunction readFunction, int alignment, byte[] bigEndianData, byte[] littleEndianData, Action? assertEquals = null) { assertEquals ??= (T expected, T value) => Assert.Equal(expected, value);