Skip to content

Commit fde8a3b

Browse files
adamsitnikbuyaa-n
andauthored
[release/9.0-rc2] NRBF Fuzzer and bug fixes (dotnet#107788)
* [NRBF] Don't use Unsafe.As when decoding DateTime(s) (dotnet#105749) * Add NrbfDecoder Fuzzer (dotnet#107385) * [NRBF] Fix bugs discovered by the fuzzer (dotnet#107368) * bug #1: don't allow for values out of the SerializationRecordType enum range * bug #2: throw SerializationException rather than KeyNotFoundException when the referenced record is missing or it points to a record of different type * bug #3: throw SerializationException rather than FormatException when it's being thrown by BinaryReader (or sth else that we use) * bug #4: document the fact that IOException can be thrown * bug #5: throw SerializationException rather than OverflowException when parsing the decimal fails * bug #6: 0 and 17 are illegal values for PrimitiveType enum * bug #7: throw SerializationException when a surrogate character is read (so far an ArgumentException was thrown) # Conflicts: # src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/NrbfDecoder.cs * [NRBF] throw SerializationException when a surrogate character is read (dotnet#107532) (so far an ArgumentException was thrown) * [NRBF] Fuzzing non-seekable stream input (dotnet#107605) * [NRBF] More bug fixes (dotnet#107682) - Don't use `Debug.Fail` not followed by an exception (it may cause problems for apps deployed in Debug) - avoid Int32 overflow - throw for unexpected enum values just in case parsing has not rejected them - validate the number of chars read by BinaryReader.ReadChars - pass serialization record id to ex message - return false rather than throw EndOfStreamException when provided Stream has not enough data - don't restore the position in finally - limit max SZ and MD array length to Array.MaxLength, stop using LinkedList<T> as List<T> will be able to hold all elements now - remove internal enum values that were always illegal, but needed to be handled everywhere - Fix DebuggerDisplay * [NRBF] Comments and bug fixes from internal code review (dotnet#107735) * copy comments and asserts from Levis internal code review * apply Levis suggestion: don't store Array.MaxLength as a const, as it may change in the future * add missing and fix some of the existing comments * first bug fix: SerializationRecord.TypeNameMatches should throw ArgumentNullException for null Type argument * second bug fix: SerializationRecord.TypeNameMatches should know the difference between SZArray and single-dimension, non-zero offset arrays (example: int[] and int[*]) * third bug fix: don't cast bytes to booleans * fourth bug fix: don't cast bytes to DateTimes * add one test case that I've forgot in previous PR # Conflicts: # src/libraries/System.Formats.Nrbf/src/System/Formats/Nrbf/SerializationRecord.cs * [NRBF] Address issues discovered by Threat Model (dotnet#106629) * introduce ArrayRecord.FlattenedLength * do not include invalid Type or Assembly names in the exception messages, as it's most likely corrupted/tampered/malicious data and could be used as a vector of attack. * It is possible to have binary array records have an element type of array without being marked as jagged --------- Co-authored-by: Buyaa Namnan <[email protected]>
1 parent fc781c3 commit fde8a3b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1179
-239
lines changed

eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ extends:
9797
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
9898
displayName: Send JsonDocumentFuzzer to OneFuzz
9999

100+
- task: onefuzz-task@0
101+
inputs:
102+
onefuzzOSes: 'Windows'
103+
env:
104+
onefuzzDropDirectory: $(fuzzerProject)/deployment/NrbfDecoderFuzzer
105+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
106+
displayName: Send NrbfDecoderFuzzer to OneFuzz
107+
100108
- task: onefuzz-task@0
101109
inputs:
102110
onefuzzOSes: 'Windows'

src/libraries/Fuzzing/DotnetFuzzing/Assert.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ static void Throw(T expected, T actual) =>
1818
throw new Exception($"Expected={expected} Actual={actual}");
1919
}
2020

21+
public static void NotNull<T>(T value)
22+
{
23+
if (value == null)
24+
{
25+
ThrowNull();
26+
}
27+
28+
static void ThrowNull() =>
29+
throw new Exception("Value is null");
30+
}
31+
2132
public static void SequenceEqual<T>(ReadOnlySpan<T> expected, ReadOnlySpan<T> actual)
2233
{
2334
if (!expected.SequenceEqual(actual))
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# "Hello World!"
2+
"\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x00\x00\x00\x00\x06\x01\x00\x00\x00\x0C\x48\x65\x6C\x6C\x6F\x20\x57\x6F\x72\x6C\x64\x21\x0B"
3+
# new DateTime(2024, 2, 29)
4+
"\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x00\x0F\x53\x79\x73\x74\x65\x6D\x2E\x44\x61\x74\x65\x54\x69\x6D\x65\x02\x00\x00\x00\x05\x74\x69\x63\x6B\x73\x08\x64\x61\x74\x65\x44\x61\x74\x61\x00\x00\x09\x10\x00\x00\x60\x5F\xB9\x38\xDC\x08\x00\x00\x60\x5F\xB9\x38\xDC\x08\x0B"
5+
# new int[] { 1, 2, 3 }
6+
"\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x00\x00\x00\x00\x0F\x01\x00\x00\x00\x03\x00\x00\x00\x08\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x0B"
7+
# new object[] { int.MaxValue, "string", null }
8+
"\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x00\x00\x00\x00\x10\x01\x00\x00\x00\x03\x00\x00\x00\x08\x08\xFF\xFF\xFF\x7F\x06\x02\x00\x00\x00\x06\x73\x74\x72\x69\x6E\x67\x0A\x0B"
9+
# new int?[Array.MaxLength] (plenty of nulls)
10+
"\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x00\x00\x00\x00\x07\x01\x00\x00\x00\x00\x01\x00\x00\x00\xC7\xFF\xFF\x7F\x03\x6E\x53\x79\x73\x74\x65\x6D\x2E\x4E\x75\x6C\x6C\x61\x62\x6C\x65\x60\x31\x5B\x5B\x53\x79\x73\x74\x65\x6D\x2E\x49\x6E\x74\x33\x32\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x5D\x0E\xC7\xFF\xFF\x7F\x0B"
11+
# [["jagged", "array"], ["of", "strings"]]
12+
"\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x00\x00\x00\x00\x07\x01\x00\x00\x00\x01\x01\x00\x00\x00\x02\x00\x00\x00\x06\x09\x02\x00\x00\x00\x09\x03\x00\x00\x00\x11\x02\x00\x00\x00\x02\x00\x00\x00\x06\x04\x00\x00\x00\x06\x6A\x61\x67\x67\x65\x64\x06\x05\x00\x00\x00\x05\x61\x72\x72\x61\x79\x11\x03\x00\x00\x00\x02\x00\x00\x00\x06\x06\x00\x00\x00\x02\x6F\x66\x06\x07\x00\x00\x00\x07\x73\x74\x72\x69\x6E\x67\x73\x0B"
13+
# new Dictionary<string, bool> { { "1", true }, { "2", false } }
14+
"\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x00\xE3\x01\x53\x79\x73\x74\x65\x6D\x2E\x43\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x73\x2E\x47\x65\x6E\x65\x72\x69\x63\x2E\x44\x69\x63\x74\x69\x6F\x6E\x61\x72\x79\x60\x32\x5B\x5B\x53\x79\x73\x74\x65\x6D\x2E\x53\x74\x72\x69\x6E\x67\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x2C\x5B\x53\x79\x73\x74\x65\x6D\x2E\x42\x6F\x6F\x6C\x65\x61\x6E\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x5D\x04\x00\x00\x00\x07\x56\x65\x72\x73\x69\x6F\x6E\x08\x43\x6F\x6D\x70\x61\x72\x65\x72\x08\x48\x61\x73\x68\x53\x69\x7A\x65\x0D\x4B\x65\x79\x56\x61\x6C\x75\x65\x50\x61\x69\x72\x73\x00\x03\x00\x03\x08\x92\x01\x53\x79\x73\x74\x65\x6D\x2E\x43\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x73\x2E\x47\x65\x6E\x65\x72\x69\x63\x2E\x47\x65\x6E\x65\x72\x69\x63\x45\x71\x75\x61\x6C\x69\x74\x79\x43\x6F\x6D\x70\x61\x72\x65\x72\x60\x31\x5B\x5B\x53\x79\x73\x74\x65\x6D\x2E\x53\x74\x72\x69\x6E\x67\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x5D\x08\xE7\x01\x53\x79\x73\x74\x65\x6D\x2E\x43\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x73\x2E\x47\x65\x6E\x65\x72\x69\x63\x2E\x4B\x65\x79\x56\x61\x6C\x75\x65\x50\x61\x69\x72\x60\x32\x5B\x5B\x53\x79\x73\x74\x65\x6D\x2E\x53\x74\x72\x69\x6E\x67\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x2C\x5B\x53\x79\x73\x74\x65\x6D\x2E\x42\x6F\x6F\x6C\x65\x61\x6E\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x5D\x5B\x5D\x02\x00\x00\x00\x09\x02\x00\x00\x00\x03\x00\x00\x00\x09\x03\x00\x00\x00\x04\x02\x00\x00\x00\x92\x01\x53\x79\x73\x74\x65\x6D\x2E\x43\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x73\x2E\x47\x65\x6E\x65\x72\x69\x63\x2E\x47\x65\x6E\x65\x72\x69\x63\x45\x71\x75\x61\x6C\x69\x74\x79\x43\x6F\x6D\x70\x61\x72\x65\x72\x60\x31\x5B\x5B\x53\x79\x73\x74\x65\x6D\x2E\x53\x74\x72\x69\x6E\x67\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x5D\x00\x00\x00\x00\x07\x03\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\xE5\x01\x53\x79\x73\x74\x65\x6D\x2E\x43\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x73\x2E\x47\x65\x6E\x65\x72\x69\x63\x2E\x4B\x65\x79\x56\x61\x6C\x75\x65\x50\x61\x69\x72\x60\x32\x5B\x5B\x53\x79\x73\x74\x65\x6D\x2E\x53\x74\x72\x69\x6E\x67\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x2C\x5B\x53\x79\x73\x74\x65\x6D\x2E\x42\x6F\x6F\x6C\x65\x61\x6E\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x5D\x04\xFC\xFF\xFF\xFF\xE5\x01\x53\x79\x73\x74\x65\x6D\x2E\x43\x6F\x6C\x6C\x65\x63\x74\x69\x6F\x6E\x73\x2E\x47\x65\x6E\x65\x72\x69\x63\x2E\x4B\x65\x79\x56\x61\x6C\x75\x65\x50\x61\x69\x72\x60\x32\x5B\x5B\x53\x79\x73\x74\x65\x6D\x2E\x53\x74\x72\x69\x6E\x67\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x2C\x5B\x53\x79\x73\x74\x65\x6D\x2E\x42\x6F\x6F\x6C\x65\x61\x6E\x2C\x20\x6D\x73\x63\x6F\x72\x6C\x69\x62\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x34\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x62\x37\x37\x61\x35\x63\x35\x36\x31\x39\x33\x34\x65\x30\x38\x39\x5D\x5D\x02\x00\x00\x00\x03\x6B\x65\x79\x05\x76\x61\x6C\x75\x65\x01\x00\x01\x06\x05\x00\x00\x00\x01\x31\x01\x01\xFA\xFF\xFF\xFF\xFC\xFF\xFF\xFF\x06\x07\x00\x00\x00\x01\x32\x00\x0B"
15+
# new ComplexType2D { I = 1, J = 2 } (non-system class)
16+
"\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF\x01\x00\x00\x00\x00\x00\x00\x00\x0C\x02\x00\x00\x00\x3D\x42\x66\x44\x65\x6D\x6F\x2C\x20\x56\x65\x72\x73\x69\x6F\x6E\x3D\x31\x2E\x30\x2E\x30\x2E\x30\x2C\x20\x43\x75\x6C\x74\x75\x72\x65\x3D\x6E\x65\x75\x74\x72\x61\x6C\x2C\x20\x50\x75\x62\x6C\x69\x63\x4B\x65\x79\x54\x6F\x6B\x65\x6E\x3D\x6E\x75\x6C\x6C\x05\x01\x00\x00\x00\x14\x42\x66\x44\x65\x6D\x6F\x2E\x43\x6F\x6D\x70\x6C\x65\x78\x54\x79\x70\x65\x32\x44\x02\x00\x00\x00\x01\x49\x01\x4A\x00\x00\x08\x08\x02\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x0B"

src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@
3030
</None>
3131
</ItemGroup>
3232

33+
<ItemGroup>
34+
<ProjectReference Include="..\..\System.Formats.Nrbf\src\System.Formats.Nrbf.csproj" />
35+
</ItemGroup>
36+
3337
</Project>

src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/AssemblyNameInfoFuzzer.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ public void FuzzTarget(ReadOnlySpan<byte> bytes)
2424
using PooledBoundedMemory<char> inputPoisonedBefore = PooledBoundedMemory<char>.Rent(chars, PoisonPagePlacement.Before);
2525
using PooledBoundedMemory<char> inputPoisonedAfter = PooledBoundedMemory<char>.Rent(chars, PoisonPagePlacement.After);
2626

27-
Test(inputPoisonedBefore);
28-
Test(inputPoisonedAfter);
27+
Test(inputPoisonedBefore.Span);
28+
Test(inputPoisonedAfter.Span);
2929
}
3030

31-
private static void Test(PooledBoundedMemory<char> inputPoisoned)
31+
private static void Test(Span<char> span)
3232
{
33-
if (AssemblyNameInfo.TryParse(inputPoisoned.Span, out AssemblyNameInfo? fromTryParse))
33+
if (AssemblyNameInfo.TryParse(span, out AssemblyNameInfo? fromTryParse))
3434
{
35-
AssemblyNameInfo fromParse = AssemblyNameInfo.Parse(inputPoisoned.Span);
35+
AssemblyNameInfo fromParse = AssemblyNameInfo.Parse(span);
3636

3737
Assert.Equal(fromTryParse.Name, fromParse.Name);
3838
Assert.Equal(fromTryParse.FullName, fromParse.FullName);
@@ -66,7 +66,7 @@ private static void Test(PooledBoundedMemory<char> inputPoisoned)
6666
{
6767
try
6868
{
69-
_ = AssemblyNameInfo.Parse(inputPoisoned.Span);
69+
_ = AssemblyNameInfo.Parse(span);
7070
}
7171
catch (ArgumentException)
7272
{
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Buffers;
5+
using System.Formats.Nrbf;
6+
using System.Runtime.Serialization;
7+
using System.Text;
8+
9+
namespace DotnetFuzzing.Fuzzers
10+
{
11+
internal sealed class NrbfDecoderFuzzer : IFuzzer
12+
{
13+
public string[] TargetAssemblies { get; } = ["System.Formats.Nrbf"];
14+
15+
public string[] TargetCoreLibPrefixes => [];
16+
17+
public string Dictionary => "nrbfdecoder.dict";
18+
19+
public void FuzzTarget(ReadOnlySpan<byte> bytes)
20+
{
21+
Test(bytes, PoisonPagePlacement.Before);
22+
Test(bytes, PoisonPagePlacement.After);
23+
}
24+
25+
private static void Test(ReadOnlySpan<byte> bytes, PoisonPagePlacement poisonPagePlacement)
26+
{
27+
using PooledBoundedMemory<byte> inputPoisoned = PooledBoundedMemory<byte>.Rent(bytes, poisonPagePlacement);
28+
29+
using MemoryStream seekableStream = new(inputPoisoned.Memory.ToArray());
30+
Test(inputPoisoned.Span, seekableStream);
31+
32+
// NrbfDecoder has few code paths dedicated to non-seekable streams, let's test them as well.
33+
using NonSeekableStream nonSeekableStream = new(inputPoisoned.Memory.ToArray());
34+
Test(inputPoisoned.Span, nonSeekableStream);
35+
}
36+
37+
private static void Test(Span<byte> testSpan, Stream stream)
38+
{
39+
if (NrbfDecoder.StartsWithPayloadHeader(testSpan))
40+
{
41+
try
42+
{
43+
SerializationRecord record = NrbfDecoder.Decode(stream, out IReadOnlyDictionary<SerializationRecordId, SerializationRecord> recordMap);
44+
switch (record.RecordType)
45+
{
46+
case SerializationRecordType.ArraySingleObject:
47+
SZArrayRecord<object?> arrayObj = (SZArrayRecord<object?>)record;
48+
object?[] objArray = arrayObj.GetArray();
49+
Assert.Equal(arrayObj.Length, objArray.Length);
50+
Assert.Equal(1, arrayObj.Rank);
51+
break;
52+
case SerializationRecordType.ArraySingleString:
53+
SZArrayRecord<string?> arrayString = (SZArrayRecord<string?>)record;
54+
string?[] array = arrayString.GetArray();
55+
Assert.Equal(arrayString.Length, array.Length);
56+
Assert.Equal(1, arrayString.Rank);
57+
Assert.Equal(true, arrayString.TypeNameMatches(typeof(string[])));
58+
break;
59+
case SerializationRecordType.ArraySinglePrimitive:
60+
case SerializationRecordType.BinaryArray:
61+
ArrayRecord arrayBinary = (ArrayRecord)record;
62+
Assert.NotNull(arrayBinary.TypeName);
63+
break;
64+
case SerializationRecordType.BinaryObjectString:
65+
_ = ((PrimitiveTypeRecord<string>)record).Value;
66+
break;
67+
case SerializationRecordType.ClassWithId:
68+
case SerializationRecordType.ClassWithMembersAndTypes:
69+
case SerializationRecordType.SystemClassWithMembersAndTypes:
70+
ClassRecord classRecord = (ClassRecord)record;
71+
Assert.NotNull(classRecord.TypeName);
72+
73+
foreach (string name in classRecord.MemberNames)
74+
{
75+
Assert.Equal(true, classRecord.HasMember(name));
76+
}
77+
break;
78+
case SerializationRecordType.MemberPrimitiveTyped:
79+
PrimitiveTypeRecord primitiveType = (PrimitiveTypeRecord)record;
80+
Assert.NotNull(primitiveType.Value);
81+
break;
82+
case SerializationRecordType.MemberReference:
83+
Assert.NotNull(record.TypeName);
84+
break;
85+
case SerializationRecordType.BinaryLibrary:
86+
Assert.Equal(false, record.Id.Equals(default));
87+
break;
88+
case SerializationRecordType.ObjectNull:
89+
case SerializationRecordType.ObjectNullMultiple:
90+
case SerializationRecordType.ObjectNullMultiple256:
91+
Assert.Equal(default, record.Id);
92+
break;
93+
case SerializationRecordType.MessageEnd:
94+
case SerializationRecordType.SerializedStreamHeader:
95+
// case SerializationRecordType.ClassWithMembers: will cause NotSupportedException
96+
// case SerializationRecordType.SystemClassWithMembers: will cause NotSupportedException
97+
default:
98+
throw new Exception("Unexpected RecordType");
99+
}
100+
}
101+
catch (SerializationException) { /* Reading from the stream encountered invalid NRBF data.*/ }
102+
catch (NotSupportedException) { /* Reading from the stream encountered unsupported records */ }
103+
catch (DecoderFallbackException) { /* Reading from the stream encountered an invalid UTF8 sequence. */ }
104+
catch (EndOfStreamException) { /* The end of the stream was reached before reading SerializationRecordType.MessageEnd record. */ }
105+
catch (IOException) { /* An I/O error occurred. */ }
106+
}
107+
else
108+
{
109+
try
110+
{
111+
NrbfDecoder.Decode(stream);
112+
throw new Exception("Decoding supposed to fail!");
113+
}
114+
catch (SerializationException) { /* Everything has to start with a header */ }
115+
catch (NotSupportedException) { /* Reading from the stream encountered unsupported records */ }
116+
catch (EndOfStreamException) { /* The end of the stream was reached before reading SerializationRecordType.MessageEnd record. */ }
117+
}
118+
}
119+
120+
private class NonSeekableStream : MemoryStream
121+
{
122+
public NonSeekableStream(byte[] buffer) : base(buffer) { }
123+
public override bool CanSeek => false;
124+
}
125+
}
126+
}

src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TypeNameFuzzer.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33

44
using System.Buffers;
55
using System.Reflection.Metadata;
6-
using System.Runtime.InteropServices;
7-
using System.Runtime.InteropServices.Marshalling;
86
using System.Text;
97

108
namespace DotnetFuzzing.Fuzzers
@@ -55,7 +53,7 @@ private static void Test(Span<char> testSpan)
5553
try
5654
{
5755
TypeName.Parse(testSpan);
58-
Assert.Equal(true, false); // should never succeed
56+
throw new Exception("Parsing was supposed to fail!");
5957
}
6058
catch (ArgumentException) { }
6159
catch (InvalidOperationException) { }

src/libraries/System.Formats.Nrbf/ref/System.Formats.Nrbf.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public abstract partial class ArrayRecord : System.Formats.Nrbf.SerializationRec
1111
internal ArrayRecord() { }
1212
public override System.Formats.Nrbf.SerializationRecordId Id { get { throw null; } }
1313
public abstract System.ReadOnlySpan<int> Lengths { get; }
14+
public virtual long FlattenedLength { get; }
1415
public int Rank { get { throw null; } }
1516
[System.Diagnostics.CodeAnalysis.RequiresDynamicCode("The code for an array of the specified type might not be available.")]
1617
public System.Array GetArray(System.Type expectedArrayType, bool allowNulls = true) { throw null; }

0 commit comments

Comments
 (0)