Skip to content

Commit

Permalink
Validate performance counter data
Browse files Browse the repository at this point in the history
Validate performance counter data to avoid excessive looping and memory consumption.
  • Loading branch information
steveharter authored and carlossanlop committed Sep 17, 2024
1 parent 10507ab commit 5bb9659
Show file tree
Hide file tree
Showing 9 changed files with 634 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.InteropServices;

Expand All @@ -13,6 +14,17 @@ internal static partial class Advapi32
internal struct PERF_COUNTER_BLOCK
{
internal int ByteLength;

internal static readonly int SizeOf = Marshal.SizeOf<PERF_COUNTER_BLOCK>();

public readonly void Validate(int bufferSize)
{
if (ByteLength < SizeOf ||
ByteLength > bufferSize)
{
ThrowInvalidOperationException(typeof(PERF_COUNTER_BLOCK));
}
}
}

[StructLayout(LayoutKind.Sequential)]
Expand All @@ -28,6 +40,20 @@ internal struct PERF_COUNTER_DEFINITION
internal int CounterType;
internal int CounterSize;
internal int CounterOffset;

internal static readonly int SizeOf = Marshal.SizeOf<PERF_COUNTER_DEFINITION>();

public readonly void Validate(int bufferSize)
{
if (ByteLength < SizeOf ||
ByteLength > bufferSize ||
CounterSize < 0 ||
CounterOffset < 0 ||
CounterOffset > bufferSize)
{
ThrowInvalidOperationException(typeof(PERF_COUNTER_DEFINITION));
}
}
}

[StructLayout(LayoutKind.Sequential)]
Expand All @@ -49,6 +75,23 @@ internal struct PERF_DATA_BLOCK
internal long PerfTime100nSec;
internal int SystemNameLength;
internal int SystemNameOffset;

internal const int Signature1Int = (int)'P' + ('E' << 16);
internal const int Signature2Int = (int)'R' + ('F' << 16);
internal static readonly int SizeOf = Marshal.SizeOf<PERF_DATA_BLOCK>();

public readonly void Validate(int bufferSize)
{
if (Signature1 != Signature1Int ||
Signature2 != Signature2Int ||
TotalByteLength < SizeOf ||
TotalByteLength > bufferSize ||
HeaderLength < SizeOf ||
HeaderLength > TotalByteLength)
{
ThrowInvalidOperationException(typeof(PERF_DATA_BLOCK));
}
}
}

[StructLayout(LayoutKind.Sequential)]
Expand All @@ -61,9 +104,23 @@ internal struct PERF_INSTANCE_DEFINITION
internal int NameOffset;
internal int NameLength;

internal static readonly int SizeOf = Marshal.SizeOf<PERF_INSTANCE_DEFINITION>();

internal static ReadOnlySpan<char> GetName(in PERF_INSTANCE_DEFINITION instance, ReadOnlySpan<byte> data)
=> (instance.NameLength == 0) ? default
: MemoryMarshal.Cast<byte, char>(data.Slice(instance.NameOffset, instance.NameLength - sizeof(char))); // NameLength includes the null-terminator

public readonly void Validate(int bufferSize)
{
if (ByteLength < SizeOf ||
ByteLength > bufferSize ||
NameOffset < 0 ||
NameLength < 0 ||
checked (NameOffset + NameLength) > ByteLength)
{
ThrowInvalidOperationException(typeof(PERF_INSTANCE_DEFINITION));
}
}
}

[StructLayout(LayoutKind.Sequential)]
Expand All @@ -83,6 +140,29 @@ internal struct PERF_OBJECT_TYPE
internal int CodePage;
internal long PerfTime;
internal long PerfFreq;

internal static readonly int SizeOf = Marshal.SizeOf<PERF_OBJECT_TYPE>();

public readonly void Validate(int bufferSize)
{
if (HeaderLength < SizeOf ||
HeaderLength > TotalByteLength ||
HeaderLength > DefinitionLength ||
DefinitionLength < SizeOf ||
DefinitionLength > TotalByteLength ||
TotalByteLength > bufferSize ||
NumCounters < 0 ||
checked
(
// This is a simple check, not exact, since it depends on how instances are specified.
(NumInstances <= 0 ? 0 : NumInstances * PERF_INSTANCE_DEFINITION.SizeOf) +
NumCounters * PERF_COUNTER_DEFINITION.SizeOf
) > bufferSize
)
{
ThrowInvalidOperationException(typeof(PERF_OBJECT_TYPE));
}
}
}

[StructLayout(LayoutKind.Sequential)]
Expand All @@ -106,4 +186,8 @@ public override string ToString()
}
}
}

[DoesNotReturn]
public static void ThrowInvalidOperationException(Type type) =>
throw new InvalidOperationException(SR.Format(SR.InvalidPerfData, type.Name));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("System.Diagnostics.PerformanceCounter.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001004b86c4cb78549b34bab61a3b1800e23bfeb5b3ec390074041536a7e3cbd97f5f04cf0f857155a8928eaa29ebfd11cfbbad3ba70efea7bda3226c6a8d370a4cd303f714486b6ebc225985a638471e6ef571cc92a4613c00b8fa65d61ccee0cbe5f36330c9a01f4183559f1bef24cc2917c6d913e3a541333a1d05d9bed22b38cb")]
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<root>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Expand Down Expand Up @@ -283,7 +284,7 @@
</data>
<data name="PlatformNotSupported_PerfCounters" xml:space="preserve">
<value>Performance Counters are not supported on this platform.</value>
</data>
</data>
<data name="Perflib_Argument_InvalidCounterSetInstanceType" xml:space="preserve">
<value>CounterSetInstanceType '{0}' is not a valid CounterSetInstanceType.</value>
</data>
Expand Down Expand Up @@ -344,4 +345,7 @@
<data name="UnauthorizedAccess_RegistryKeyGeneric_Key" xml:space="preserve">
<value>Access to the registry key '{0}' is denied.</value>
</data>
</root>
<data name="InvalidPerfData" xml:space="preserve">
<value>Invalid performance counter data with type '{0}'.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ System.Diagnostics.PerformanceCounter</PackageDescription>
</PropertyGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
<Compile Include="Properties\InternalsVisibleTo.cs" />
<Compile Include="System\Diagnostics\DiagnosticsConfiguration.cs" />
<Compile Include="System\Diagnostics\CounterCreationData.cs" />
<Compile Include="System\Diagnostics\CounterCreationDataCollection.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,7 @@ internal static string ComputerName
}
}

#if !NET
internal static T Read<T>(ReadOnlySpan<byte> span) where T : struct
=> System.Runtime.InteropServices.MemoryMarshal.Read<T>(span);

internal static ref readonly T AsRef<T>(ReadOnlySpan<byte> span) where T : struct
=> ref System.Runtime.InteropServices.MemoryMarshal.Cast<byte, T>(span)[0];
#endif

private Hashtable CategoryTable
internal Hashtable CategoryTable
{
get
{
Expand All @@ -141,6 +133,8 @@ private Hashtable CategoryTable
ReadOnlySpan<byte> data = GetPerformanceData("Global");

ref readonly PERF_DATA_BLOCK dataBlock = ref MemoryMarshal.AsRef<PERF_DATA_BLOCK>(data);
dataBlock.Validate(data.Length);

int pos = dataBlock.HeaderLength;

int numPerfObjects = dataBlock.NumObjectTypes;
Expand All @@ -152,7 +146,9 @@ private Hashtable CategoryTable
Hashtable tempCategoryTable = new Hashtable(numPerfObjects, StringComparer.OrdinalIgnoreCase);
for (int index = 0; index < numPerfObjects && pos < dataBlock.TotalByteLength; index++)
{
ref readonly PERF_OBJECT_TYPE perfObject = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(data.Slice(pos));
ReadOnlySpan<byte> dataSpan = data.Slice(pos);
ref readonly PERF_OBJECT_TYPE perfObject = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(dataSpan);
perfObject.Validate(dataSpan.Length);

CategoryEntry newCategoryEntry = new CategoryEntry(in perfObject);
int nextPos = pos + perfObject.TotalByteLength;
Expand All @@ -164,7 +160,10 @@ private Hashtable CategoryTable
//return several adjacent copies of the same counter.
for (int index2 = 0; index2 < newCategoryEntry.CounterIndexes.Length; ++index2)
{
ref readonly PERF_COUNTER_DEFINITION perfCounter = ref MemoryMarshal.AsRef<PERF_COUNTER_DEFINITION>(data.Slice(pos));
dataSpan = data.Slice(pos);
ref readonly PERF_COUNTER_DEFINITION perfCounter = ref MemoryMarshal.AsRef<PERF_COUNTER_DEFINITION>(dataSpan);
perfCounter.Validate(dataSpan.Length);

if (perfCounter.CounterNameTitleIndex != previousCounterIndex)
{
newCategoryEntry.CounterIndexes[index3] = perfCounter.CounterNameTitleIndex;
Expand Down Expand Up @@ -1392,6 +1391,7 @@ internal CategorySample(byte[] rawData, CategoryEntry entry, PerformanceCounterL
int categoryIndex = entry.NameIndex;

ref readonly PERF_DATA_BLOCK dataBlock = ref MemoryMarshal.AsRef<PERF_DATA_BLOCK>(data);
dataBlock.Validate(data.Length);

_systemFrequency = dataBlock.PerfFreq;
_timeStamp = dataBlock.PerfTime;
Expand All @@ -1408,9 +1408,12 @@ internal CategorySample(byte[] rawData, CategoryEntry entry, PerformanceCounterL
//Need to find the right category, GetPerformanceData might return
//several of them.
bool foundCategory = false;
ReadOnlySpan<byte> dataSpan;
for (int index = 0; index < numPerfObjects; index++)
{
ref readonly PERF_OBJECT_TYPE perfObjectType = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(data.Slice(pos));
dataSpan = data.Slice(pos);
ref readonly PERF_OBJECT_TYPE perfObjectType = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(dataSpan);
perfObjectType.Validate(dataSpan.Length);

if (perfObjectType.ObjectNameTitleIndex == categoryIndex)
{
Expand All @@ -1424,7 +1427,9 @@ internal CategorySample(byte[] rawData, CategoryEntry entry, PerformanceCounterL
if (!foundCategory)
throw new InvalidOperationException(SR.Format(SR.CantReadCategoryIndex, categoryIndex.ToString()));

ref readonly PERF_OBJECT_TYPE perfObject = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(data.Slice(pos));
dataSpan = data.Slice(pos);
ref readonly PERF_OBJECT_TYPE perfObject = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(dataSpan);
// We already validated this object above.

_counterFrequency = perfObject.PerfFreq;
_counterTimeStamp = perfObject.PerfTime;
Expand All @@ -1443,7 +1448,10 @@ internal CategorySample(byte[] rawData, CategoryEntry entry, PerformanceCounterL
_counterTable = new Hashtable(counterNumber);
for (int index = 0; index < samples.Length; ++index)
{
ref readonly PERF_COUNTER_DEFINITION perfCounter = ref MemoryMarshal.AsRef<PERF_COUNTER_DEFINITION>(data.Slice(pos));
dataSpan = data.Slice(pos);
ref readonly PERF_COUNTER_DEFINITION perfCounter = ref MemoryMarshal.AsRef<PERF_COUNTER_DEFINITION>(dataSpan);
perfCounter.Validate(dataSpan.Length);

samples[index] = new CounterDefinitionSample(in perfCounter, this, instanceNumber);
pos += perfCounter.ByteLength;

Expand Down Expand Up @@ -1480,7 +1488,10 @@ internal CategorySample(byte[] rawData, CategoryEntry entry, PerformanceCounterL
_instanceNameTable = new Hashtable(instanceNumber, StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < instanceNumber; i++)
{
ref readonly PERF_INSTANCE_DEFINITION perfInstance = ref MemoryMarshal.AsRef<PERF_INSTANCE_DEFINITION>(data.Slice(pos));
dataSpan = data.Slice(pos);
ref readonly PERF_INSTANCE_DEFINITION perfInstance = ref MemoryMarshal.AsRef<PERF_INSTANCE_DEFINITION>(dataSpan);
perfInstance.Validate(dataSpan.Length);

if (perfInstance.ParentObjectTitleIndex > 0 && parentInstanceNames == null)
parentInstanceNames = GetInstanceNamesFromIndex(perfInstance.ParentObjectTitleIndex);

Expand All @@ -1506,13 +1517,16 @@ internal CategorySample(byte[] rawData, CategoryEntry entry, PerformanceCounterL
}
}


pos += perfInstance.ByteLength;

for (int index = 0; index < samples.Length; ++index)
samples[index].SetInstanceValue(i, data.Slice(pos));

pos += MemoryMarshal.AsRef<PERF_COUNTER_BLOCK>(data.Slice(pos)).ByteLength;
dataSpan = data.Slice(pos);
ref readonly PERF_COUNTER_BLOCK perfCounterBlock = ref MemoryMarshal.AsRef<PERF_COUNTER_BLOCK>(dataSpan);
perfCounterBlock.Validate(dataSpan.Length);

pos += perfCounterBlock.ByteLength;
}
}
}
Expand All @@ -1524,13 +1538,18 @@ internal string[] GetInstanceNamesFromIndex(int categoryIndex)
ReadOnlySpan<byte> data = _library.GetPerformanceData(categoryIndex.ToString(CultureInfo.InvariantCulture));

ref readonly PERF_DATA_BLOCK dataBlock = ref MemoryMarshal.AsRef<PERF_DATA_BLOCK>(data);
dataBlock.Validate(data.Length);

int pos = dataBlock.HeaderLength;
int numPerfObjects = dataBlock.NumObjectTypes;

bool foundCategory = false;
ReadOnlySpan<byte> dataSpan;
for (int index = 0; index < numPerfObjects; index++)
{
ref readonly PERF_OBJECT_TYPE type = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(data.Slice(pos));
dataSpan = data.Slice(pos);
ref readonly PERF_OBJECT_TYPE type = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(dataSpan);
type.Validate(dataSpan.Length);

if (type.ObjectNameTitleIndex == categoryIndex)
{
Expand All @@ -1544,7 +1563,9 @@ internal string[] GetInstanceNamesFromIndex(int categoryIndex)
if (!foundCategory)
return Array.Empty<string>();

ref readonly PERF_OBJECT_TYPE perfObject = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(data.Slice(pos));
dataSpan = data.Slice(pos);
ref readonly PERF_OBJECT_TYPE perfObject = ref MemoryMarshal.AsRef<PERF_OBJECT_TYPE>(dataSpan);
perfObject.Validate(dataSpan.Length);

int counterNumber = perfObject.NumCounters;
int instanceNumber = perfObject.NumInstances;
Expand All @@ -1556,17 +1577,28 @@ internal string[] GetInstanceNamesFromIndex(int categoryIndex)
CounterDefinitionSample[] samples = new CounterDefinitionSample[counterNumber];
for (int index = 0; index < samples.Length; ++index)
{
pos += MemoryMarshal.AsRef<PERF_COUNTER_DEFINITION>(data.Slice(pos)).ByteLength;
dataSpan = data.Slice(pos);
ref readonly PERF_COUNTER_DEFINITION perfCounterDefinition = ref MemoryMarshal.AsRef<PERF_COUNTER_DEFINITION>(dataSpan);
perfCounterDefinition.Validate(dataSpan.Length);

pos += perfCounterDefinition.ByteLength;
}

string[] instanceNames = new string[instanceNumber];
for (int i = 0; i < instanceNumber; i++)
{
ref readonly PERF_INSTANCE_DEFINITION perfInstance = ref MemoryMarshal.AsRef<PERF_INSTANCE_DEFINITION>(data.Slice(pos));
dataSpan = data.Slice(pos);
ref readonly PERF_INSTANCE_DEFINITION perfInstance = ref MemoryMarshal.AsRef<PERF_INSTANCE_DEFINITION>(dataSpan);
perfInstance.Validate(dataSpan.Length);

instanceNames[i] = PERF_INSTANCE_DEFINITION.GetName(in perfInstance, data.Slice(pos)).ToString();
pos += perfInstance.ByteLength;

pos += MemoryMarshal.AsRef<PERF_COUNTER_BLOCK>(data.Slice(pos)).ByteLength;
dataSpan = data.Slice(pos);
ref readonly PERF_COUNTER_BLOCK perfCounterBlock = ref MemoryMarshal.AsRef<PERF_COUNTER_BLOCK>(dataSpan);
perfCounterBlock.Validate(dataSpan.Length);

pos += perfCounterBlock.ByteLength;
}

return instanceNames;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,22 @@
<ItemGroup>
<Compile Include="PerformanceCounterTests.cs" />
<Compile Include="PerformanceCounterCategoryTests.cs" />
<Compile Include="ValidationTests.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
<Compile Include="CounterSampleTests.cs" />
<Compile Include="CounterSampleCalculatorTests.cs" />
<Compile Include="CounterCreationDataTests.cs" />
<Compile Include="CounterCreationDataCollectionTests.cs" />
<Compile Include="InstanceDataTests.cs" />
<Compile Include="Helpers.cs" />
<Compile Include="$(CommonTestPath)System\ShouldNotBeInvokedException.cs"
Link="Common\System\ShouldNotBeInvokedException.cs" />
<Compile Include="$(CommonTestPath)System\ShouldNotBeInvokedException.cs" Link="Common\System\ShouldNotBeInvokedException.cs" />
<Compile Include="PerformanceDataTests.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="provider.man" CopyToOutputDirectory="Always" />
<None Include="provider.res" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<ProjectReference Include="..\src\System.Diagnostics.PerformanceCounter.csproj" />
<!-- Some internal types are needed, so we reference the implementation assembly, rather than the reference assembly. -->
<ProjectReference Include="..\src\System.Diagnostics.PerformanceCounter.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>
</Project>
Loading

0 comments on commit 5bb9659

Please sign in to comment.