Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Tmds.DBus.Protocol/ProtocolConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
33 changes: 19 additions & 14 deletions src/Tmds.DBus.Protocol/Reader.Variant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,31 @@ public ref partial struct Reader
/// </summary>
/// <returns>The variant value.</returns>
public VariantValue ReadVariantValue()
=> ReadVariantValue(nesting: 0);
=> ReadVariantValue(nesting: 0, recursionDepth: 0);

private VariantValue ReadVariantValue(byte nesting)
private VariantValue ReadVariantValue(byte nesting, int recursionDepth)
{
ReadOnlySpan<byte> signature = ReadSignatureAsSpan();
return ReadVariantValue(signature, nesting);
return ReadVariantValue(signature, nesting, recursionDepth);
}

internal VariantValue ReadVariantValue(ReadOnlySpan<byte> signature, byte nesting = 0)
internal VariantValue ReadVariantValue(ReadOnlySpan<byte> signature, byte nesting = 0, int recursionDepth = 0)
{
SignatureReader sigReader = new(signature);
if (!sigReader.TryRead(out DBusType type, out ReadOnlySpan<byte> 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<byte> innerSignature, byte nesting)
private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> innerSignature, byte nesting, int recursionDepth)
{
recursionDepth++;
if (recursionDepth > ProtocolConstants.MaxVariantRecursionDepth)
{
ThrowHelper.ThrowReaderRecursionDepthExceeded();
}
SignatureReader sigReader;
switch (type)
{
Expand Down Expand Up @@ -59,7 +64,7 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> in
return new VariantValue(_handles, idx, nesting);
case DBusType.Variant:
nesting += 1;
return ReadVariantValue(nesting);
return ReadVariantValue(nesting, recursionDepth);
case DBusType.Array:
ReadOnlySpan<byte> itemSignature = innerSignature;
sigReader = new(innerSignature);
Expand All @@ -83,10 +88,10 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> 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<VariantValue, VariantValue>(key, value));
}
ReadOnlySpan<byte> valueSignature = itemSignature.Slice(2, itemSignature.Length - 3);
Expand Down Expand Up @@ -141,8 +146,8 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> 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);
Expand All @@ -161,11 +166,11 @@ private VariantValue ReadTypeAsVariantValue(DBusType type, ReadOnlySpan<byte> 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++;
Expand Down
6 changes: 6 additions & 0 deletions src/Tmds.DBus.Protocol/ThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
79 changes: 79 additions & 0 deletions test/Tmds.DBus.Protocol.Tests/ReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,85 @@ public static IEnumerable<object[]> 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<DBusReadException>(() =>
{
Reader reader = new Reader(isBigEndian: false, new System.Buffers.ReadOnlySequence<byte>(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<byte>(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<byte> 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<DBusReadException>(() =>
{
Reader reader = new Reader(isBigEndian: false, new System.Buffers.ReadOnlySequence<byte>(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<byte>(bytes.ToArray()));
reader.ReadVariantValue();
}
}

private void TestRead<T>(T expected, ReadFunction<T> readFunction, int alignment, byte[] bigEndianData, byte[] littleEndianData, Action<T, T>? assertEquals = null)
{
assertEquals ??= (T expected, T value) => Assert.Equal(expected, value);
Expand Down
Loading