diff --git a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml index 10a4dd6dc2c0a..0cde3c038ed65 100644 --- a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml +++ b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml @@ -65,6 +65,14 @@ extends: # displayName: Send to OneFuzz # ONEFUZZ_TASK_WORKAROUND_START + - task: onefuzz-task@0 + inputs: + onefuzzOSes: 'Windows' + env: + onefuzzDropDirectory: $(fuzzerProject)/deployment/AssemblyNameInfoFuzzer + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Send AssemblyNameInfoFuzzer to OneFuzz + - task: onefuzz-task@0 inputs: onefuzzOSes: 'Windows' diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/AssemblyNameInfoFuzzer.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/AssemblyNameInfoFuzzer.cs new file mode 100644 index 0000000000000..e4ce320c40f67 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/AssemblyNameInfoFuzzer.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Buffers; +using System.Reflection.Metadata; +using System.Runtime.InteropServices; + +namespace DotnetFuzzing.Fuzzers +{ + internal sealed class AssemblyNameInfoFuzzer : IFuzzer + { + public string[] TargetAssemblies => ["System.Reflection.Metadata"]; + + public string[] TargetCoreLibPrefixes => []; + + public void FuzzTarget(ReadOnlySpan bytes) + { + ReadOnlySpan chars = MemoryMarshal.Cast(bytes); + + using PooledBoundedMemory inputPoisonedBefore = PooledBoundedMemory.Rent(chars, PoisonPagePlacement.Before); + using PooledBoundedMemory inputPoisonedAfter = PooledBoundedMemory.Rent(chars, PoisonPagePlacement.After); + + Test(inputPoisonedBefore); + Test(inputPoisonedAfter); + } + + private static void Test(PooledBoundedMemory inputPoisoned) + { + bool shouldSucceed = AssemblyNameInfo.TryParse(inputPoisoned.Span, out _); + + try + { + AssemblyNameInfo.Parse(inputPoisoned.Span); + } + catch (ArgumentException) + { + Assert.Equal(false, shouldSucceed); + return; + } + + Assert.Equal(true, shouldSucceed); + } + } +} diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/AssemblyNameInfo.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/AssemblyNameInfo.cs index ddc61cd36449c..bd1febff03659 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/AssemblyNameInfo.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/AssemblyNameInfo.cs @@ -193,7 +193,7 @@ public static AssemblyNameInfo Parse(ReadOnlySpan assemblyName) public static bool TryParse(ReadOnlySpan assemblyName, [NotNullWhen(true)] out AssemblyNameInfo? result) { AssemblyNameParser.AssemblyNameParts parts = default; - if (AssemblyNameParser.TryParse(assemblyName, ref parts)) + if (!assemblyName.IsEmpty && AssemblyNameParser.TryParse(assemblyName, ref parts)) { result = new(parts); return true; diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/AssemblyNameInfoTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/AssemblyNameInfoTests.cs index 89127889c407c..c766e6bd4d559 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/AssemblyNameInfoTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/AssemblyNameInfoTests.cs @@ -98,6 +98,13 @@ public void RetargetableIsPropagated() public void EscapedSquareBracketIsNotAllowedInTheName() => Assert.False(AssemblyNameInfo.TryParse("Esc\\[aped".AsSpan(), out _)); + [Fact] + public void EmptyInputIsInvalid() + { + Assert.False(AssemblyNameInfo.TryParse("".AsSpan(), out _)); + Assert.Throws(() => AssemblyNameInfo.Parse("".AsSpan())); + } + static void Roundtrip(AssemblyName source) { AssemblyNameInfo parsed = AssemblyNameInfo.Parse(source.FullName.AsSpan());