| | | 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.Globalization; |
| | | 6 | | using System.Reflection.Metadata; |
| | | 7 | | using System.Runtime.InteropServices; |
| | | 8 | | using System.Text; |
| | | 9 | | |
| | | 10 | | namespace DotnetFuzzing.Fuzzers |
| | | 11 | | { |
| | | 12 | | internal sealed class AssemblyNameInfoFuzzer : IFuzzer |
| | | 13 | | { |
| | 0 | 14 | | public string[] TargetAssemblies => ["System.Reflection.Metadata"]; |
| | | 15 | | |
| | 0 | 16 | | public string[] TargetCoreLibPrefixes => []; |
| | | 17 | | |
| | 0 | 18 | | public string Dictionary => "assemblynameinfo.dict"; |
| | | 19 | | |
| | | 20 | | public void FuzzTarget(ReadOnlySpan<byte> bytes) |
| | 2535 | 21 | | { |
| | 2535 | 22 | | Span<char> chars = new char[Encoding.UTF8.GetCharCount(bytes)]; |
| | 2535 | 23 | | Encoding.UTF8.GetChars(bytes, chars); |
| | | 24 | | |
| | 2535 | 25 | | using PooledBoundedMemory<char> inputPoisonedBefore = PooledBoundedMemory<char>.Rent(chars, PoisonPagePlacem |
| | 2535 | 26 | | using PooledBoundedMemory<char> inputPoisonedAfter = PooledBoundedMemory<char>.Rent(chars, PoisonPagePlaceme |
| | | 27 | | |
| | 2535 | 28 | | Test(inputPoisonedBefore.Span); |
| | 2535 | 29 | | Test(inputPoisonedAfter.Span); |
| | 5070 | 30 | | } |
| | | 31 | | |
| | | 32 | | private static void Test(Span<char> span) |
| | 5070 | 33 | | { |
| | 5070 | 34 | | if (AssemblyNameInfo.TryParse(span, out AssemblyNameInfo? fromTryParse)) |
| | 2352 | 35 | | { |
| | 2352 | 36 | | AssemblyNameInfo fromParse = AssemblyNameInfo.Parse(span); |
| | | 37 | | |
| | 2352 | 38 | | Assert.Equal(fromTryParse.Name, fromParse.Name); |
| | 2352 | 39 | | Assert.Equal(fromTryParse.FullName, fromParse.FullName); |
| | 2352 | 40 | | Assert.Equal(fromTryParse.CultureName, fromParse.CultureName); |
| | 2352 | 41 | | Assert.Equal(fromTryParse.Flags, fromParse.Flags); |
| | 2352 | 42 | | Assert.Equal(fromTryParse.Version, fromParse.Version); |
| | 2352 | 43 | | Assert.SequenceEqual(fromTryParse.PublicKeyOrToken.AsSpan(), fromParse.PublicKeyOrToken.AsSpan()); |
| | | 44 | | |
| | 2352 | 45 | | if (!string.IsNullOrEmpty(fromParse.CultureName)) |
| | 0 | 46 | | { |
| | | 47 | | try |
| | 0 | 48 | | { |
| | 0 | 49 | | _ = CultureInfo.GetCultureInfo(fromParse.CultureName); |
| | 0 | 50 | | } |
| | 0 | 51 | | catch (CultureNotFoundException) |
| | 0 | 52 | | { |
| | | 53 | | // ToAssemblyName would try to create such a culture and fail. |
| | 0 | 54 | | return; |
| | | 55 | | } |
| | 0 | 56 | | } |
| | | 57 | | |
| | 2352 | 58 | | Assert.Equal(fromTryParse.ToAssemblyName().Name, fromParse.ToAssemblyName().Name); |
| | 2352 | 59 | | Assert.Equal(fromTryParse.ToAssemblyName().Version, fromParse.ToAssemblyName().Version); |
| | 2352 | 60 | | Assert.Equal(fromTryParse.ToAssemblyName().ContentType, fromParse.ToAssemblyName().ContentType); |
| | 2352 | 61 | | Assert.Equal(fromTryParse.ToAssemblyName().CultureName, fromParse.ToAssemblyName().CultureName); |
| | | 62 | | |
| | 2352 | 63 | | Assert.Equal(fromTryParse.Name, fromParse.ToAssemblyName().Name); |
| | 2352 | 64 | | Assert.Equal(fromTryParse.Version, fromParse.ToAssemblyName().Version); |
| | | 65 | | |
| | 2352 | 66 | | if (fromTryParse.CultureName is not null) |
| | 0 | 67 | | { |
| | | 68 | | // When converting to AssemblyName, the culture name is lower-cased |
| | | 69 | | // by the CultureInfo ctor that calls CultureData.GetCultureData |
| | | 70 | | // which lowers the name for caching and normalization purposes. |
| | | 71 | | // It lowers only the part before the `-` character, but we lower |
| | | 72 | | // the whole string for the sake of simplicity of this test. |
| | | 73 | | |
| | 0 | 74 | | string lowerCase = fromTryParse.CultureName.ToLower(); |
| | 0 | 75 | | if (lowerCase != "c") |
| | 0 | 76 | | { |
| | 0 | 77 | | Assert.Equal(lowerCase, fromParse.ToAssemblyName().CultureName!.ToLower()); |
| | 0 | 78 | | } |
| | | 79 | | else |
| | 0 | 80 | | { |
| | | 81 | | // Cultures "c" and "C" get mapped to Invariant Culture. |
| | 0 | 82 | | Assert.Equal("", fromParse.ToAssemblyName().CultureName); |
| | 0 | 83 | | Assert.Equal(CultureInfo.InvariantCulture, fromParse.ToAssemblyName().CultureInfo); |
| | 0 | 84 | | } |
| | 0 | 85 | | } |
| | | 86 | | else |
| | 2352 | 87 | | { |
| | 2352 | 88 | | Assert.True(fromParse.ToAssemblyName().CultureName is null); |
| | 2352 | 89 | | } |
| | | 90 | | |
| | | 91 | | // AssemblyNameInfo.FullName can be different than AssemblyName.FullName: |
| | | 92 | | // AssemblyNameInfo includes public key, AssemblyName only its Token. |
| | | 93 | | |
| | | 94 | | try |
| | 2352 | 95 | | { |
| | 2352 | 96 | | Assert.Equal(fromTryParse.ToAssemblyName().FullName, fromParse.ToAssemblyName().FullName); |
| | 2160 | 97 | | } |
| | 192 | 98 | | catch (System.Security.SecurityException) |
| | 192 | 99 | | { |
| | | 100 | | // AssemblyName.FullName performs public key validation, AssemblyNameInfo does not (on purpose). |
| | 192 | 101 | | } |
| | 2352 | 102 | | } |
| | | 103 | | else |
| | 2718 | 104 | | { |
| | | 105 | | try |
| | 2718 | 106 | | { |
| | 2718 | 107 | | _ = AssemblyNameInfo.Parse(span); |
| | 0 | 108 | | } |
| | 2718 | 109 | | catch (ArgumentException) |
| | 2718 | 110 | | { |
| | 2718 | 111 | | return; |
| | | 112 | | } |
| | | 113 | | |
| | 0 | 114 | | throw new Exception("Parsing was supposed to fail!"); |
| | | 115 | | } |
| | 5070 | 116 | | } |
| | | 117 | | } |
| | | 118 | | } |