From 53aac478687ecdc2d29e8bd3a135278186c94714 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Thu, 6 Jun 2024 15:37:13 -0500 Subject: [PATCH 1/6] Add fuzzing for System.Text encoding classes --- .../libraries/fuzzing/deploy-to-onefuzz.yml | 16 + .../DotnetFuzzing/DotnetFuzzing.csproj | 4 + .../Fuzzers/TextEncoding.NetFramework.cs | 49 +++ .../TextEncoding.NetFramework/App.config | 6 + .../TextEncoding.NetFramework/Assert.cs | 44 +++ .../BoundedMemory.Creation.cs | 84 +++++ .../BoundedMemory.Windows.cs | 328 ++++++++++++++++++ .../BoundedMemory.cs | 69 ++++ .../PoisonPagePlacement.cs | 25 ++ .../PooledBoundedMemory.cs | 72 ++++ .../TextEncoding.NetFramework/TextEncoding.cs | 212 +++++++++++ .../TextEncodingNetFramework.csproj | 74 ++++ .../TextEncoding.NetFramework/packages.config | 7 + .../DotnetFuzzing/Fuzzers/TextEncoding.cs | 178 ++++++++++ 14 files changed, 1168 insertions(+) create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/App.config create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Creation.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Windows.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PoisonPagePlacement.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PooledBoundedMemory.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncoding.cs create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.csproj create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/packages.config create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs diff --git a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml index a26558ee148ffc..00d63f36df593d 100644 --- a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml +++ b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml @@ -97,6 +97,22 @@ extends: SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Send SearchValuesStringFuzzer to OneFuzz + - task: onefuzz-task@0 + inputs: + onefuzzOSes: 'Windows' + env: + onefuzzDropDirectory: $(fuzzerProject)/deployment/TextEncoding + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Send TextEncoding to OneFuzz + + - task: onefuzz-task@0 + inputs: + onefuzzOSes: 'Windows' + env: + onefuzzDropDirectory: $(fuzzerProject)/deployment/TextEncodingNetFramework + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Send TextEncodingNetFramework to OneFuzz + - task: onefuzz-task@0 inputs: onefuzzOSes: 'Windows' diff --git a/src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj b/src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj index fa571fbbf969ca..740d367c40d703 100644 --- a/src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj +++ b/src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj @@ -11,6 +11,10 @@ true + + + + diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs new file mode 100644 index 00000000000000..d3a017ec1ec9a2 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace DotnetFuzzing.Fuzzers; + +internal sealed class TextEncodingNetFramework : IFuzzer +{ + const string TestSubDirectory = @"src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\TextEncoding.NetFramework\bin\Release\TextEncodingNetFramework.exe"; + + string[] IFuzzer.TargetAssemblies => ["mscorlib"]; // Not sure if this does anything. + string[] IFuzzer.TargetCoreLibPrefixes { get; } = []; + + void IFuzzer.FuzzTarget(ReadOnlySpan bytes) + { + string path = GetRepoRootDirectory(); + path = Path.Combine(path, TestSubDirectory); + if (!File.Exists(path)) + { + Console.WriteLine($"Cannot find the .NET Framework test executable at {path}"); + } + + string encoded = Convert.ToBase64String(bytes); + Process.Start(path, encoded); + } + + private static string GetRepoRootDirectory() + { + string? currentDirectory = Directory.GetCurrentDirectory(); + + while (currentDirectory != null) + { + string gitDirOrFile = Path.Combine(currentDirectory, ".git"); + if (Directory.Exists(gitDirOrFile) || File.Exists(gitDirOrFile)) + { + break; + } + currentDirectory = Directory.GetParent(currentDirectory)?.FullName; + } + + if (currentDirectory == null) + { + throw new Exception("Cannot find the git repository root"); + } + + return currentDirectory; + } +} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/App.config b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/App.config new file mode 100644 index 00000000000000..aee9adf485c918 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs new file mode 100644 index 00000000000000..51b80c20380594 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.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; +using System.Collections.Generic; + +namespace DotnetFuzzing +{ + internal static class Assert + { + public static void Equal(T expected, T actual) + { + if (!EqualityComparer.Default.Equals(expected, actual)) + { + throw new Exception($"Expected={expected} Actual={actual}"); + } + } + + public static void SequenceEqual(ReadOnlySpan expected, ReadOnlySpan actual) + { + if (!expected.SequenceEqual(actual)) + { + Equal(expected.Length, actual.Length); + int diffIndex = expected.CommonPrefixLength(actual); + throw new Exception($"Expected={expected[diffIndex]} Actual={actual[diffIndex]} at index {diffIndex}"); + } + } + + public static int CommonPrefixLength(this ReadOnlySpan span, ReadOnlySpan other) + { + int length = Math.Min(span.Length, other.Length); + + for (int i = 0; i < length; i++) + { + if (span[i] != other[i]) + { + return i; + } + } + + return length; + } + } +} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Creation.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Creation.cs new file mode 100644 index 00000000000000..92896510e29103 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Creation.cs @@ -0,0 +1,84 @@ +// 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.InteropServices; + +namespace System.Buffers +{ + /// + /// Contains factory methods to create instances. + /// + public static partial class BoundedMemory + { + /// + /// Allocates a new region which is immediately preceded by + /// or immediately followed by a poison (MEM_NOACCESS) page. If + /// is , then attempting to read the memory + /// immediately before the returned will result in an AV. + /// If is , then + /// attempting to read the memory immediately after the returned + /// will result in AV. + /// + /// + /// The newly-allocated memory will be populated with random data. + /// + public static BoundedMemory Allocate(int elementCount, PoisonPagePlacement placement = PoisonPagePlacement.After) where T : unmanaged + { + if (elementCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(elementCount)); + } + if (placement != PoisonPagePlacement.Before && placement != PoisonPagePlacement.After) + { + throw new ArgumentOutOfRangeException(nameof(placement)); + } + + BoundedMemory retVal = AllocateWithoutDataPopulation(elementCount, placement); + FillRandom(MemoryMarshal.AsBytes(retVal.Span)); + return retVal; + } + + /// + /// Similar to , but populates the allocated + /// native memory block from existing data rather than using random data. + /// + public static BoundedMemory AllocateFromExistingData(ReadOnlySpan data, PoisonPagePlacement placement = PoisonPagePlacement.After) where T : unmanaged + { + if (placement != PoisonPagePlacement.Before && placement != PoisonPagePlacement.After) + { + throw new ArgumentOutOfRangeException(nameof(placement)); + } + + BoundedMemory retVal = AllocateWithoutDataPopulation(data.Length, placement); + data.CopyTo(retVal.Span); + return retVal; + } + + /// + /// Similar to , but populates the allocated + /// native memory block from existing data rather than using random data. + /// + public static BoundedMemory AllocateFromExistingData(T[] data, PoisonPagePlacement placement = PoisonPagePlacement.After) where T : unmanaged + { + return AllocateFromExistingData(new ReadOnlySpan(data), placement); + } + + private static void FillRandom(Span buffer) + { + // Loop over a Random instance manually since Random.NextBytes(Span) doesn't + // exist on all platforms we target. + + Random random = new Random(); // doesn't need to be cryptographically strong + + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = (byte)random.Next(); + } + } + + private static BoundedMemory AllocateWithoutDataPopulation(int elementCount, PoisonPagePlacement placement) where T : unmanaged + { + return AllocateWithoutDataPopulationWindows(elementCount, placement); + } + } +} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Windows.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Windows.cs new file mode 100644 index 00000000000000..023566af11a936 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Windows.cs @@ -0,0 +1,328 @@ +// 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.InteropServices; + +namespace System.Buffers +{ + public static unsafe partial class BoundedMemory + { + private static readonly int SystemPageSize = Environment.SystemPageSize; + + private static WindowsImplementation AllocateWithoutDataPopulationWindows(int elementCount, PoisonPagePlacement placement) where T : unmanaged + { + long cb, totalBytesToAllocate; + checked + { + cb = elementCount * sizeof(T); + totalBytesToAllocate = cb; + + // We only need to round the count up if it's not an exact multiple + // of the system page size. + + long leftoverBytes = totalBytesToAllocate % SystemPageSize; + if (leftoverBytes != 0) + { + totalBytesToAllocate += SystemPageSize - leftoverBytes; + } + + // Finally, account for the poison pages at the front and back. + + totalBytesToAllocate += 2 * SystemPageSize; + } + + // Reserve and commit the entire range as NOACCESS. + + VirtualAllocHandle handle = UnsafeNativeMethods.VirtualAlloc( + lpAddress: IntPtr.Zero, + dwSize: (IntPtr)totalBytesToAllocate /* cast throws OverflowException if out of range */, + flAllocationType: VirtualAllocAllocationType.MEM_RESERVE | VirtualAllocAllocationType.MEM_COMMIT, + flProtect: VirtualAllocProtection.PAGE_NOACCESS); + + if (handle == null || handle.IsInvalid) + { + int lastError = Marshal.GetHRForLastWin32Error(); + handle?.Dispose(); + Marshal.ThrowExceptionForHR(lastError); + throw new InvalidOperationException("VirtualAlloc failed unexpectedly."); + } + + // Done allocating! Now carve out a READWRITE section bookended by the NOACCESS + // pages and return that carved-out section to the caller. Since memory protection + // flags only apply at page-level granularity, we need to "left-align" or "right- + // align" the section we carve out so that it's guaranteed adjacent to one of + // the NOACCESS bookend pages. + + return new WindowsImplementation( + handle: handle, + byteOffsetIntoHandle: (placement == PoisonPagePlacement.Before) + ? SystemPageSize /* just after leading poison page */ + : checked((int)(totalBytesToAllocate - SystemPageSize - cb)) /* just before trailing poison page */, + elementCount: elementCount) + { + Protection = VirtualAllocProtection.PAGE_READWRITE + }; + } + + private sealed class WindowsImplementation : BoundedMemory where T : unmanaged + { + private readonly VirtualAllocHandle _handle; + private readonly int _byteOffsetIntoHandle; + private readonly int _elementCount; + private readonly BoundedMemoryManager _memoryManager; + + internal WindowsImplementation(VirtualAllocHandle handle, int byteOffsetIntoHandle, int elementCount) + { + _handle = handle; + _byteOffsetIntoHandle = byteOffsetIntoHandle; + _elementCount = elementCount; + _memoryManager = new BoundedMemoryManager(this); + } + + public override bool IsReadonly => (Protection != VirtualAllocProtection.PAGE_READWRITE); + + public override int Length => _elementCount; + + internal VirtualAllocProtection Protection + { + get + { + bool refAdded = false; + try + { + _handle.DangerousAddRef(ref refAdded); + if (UnsafeNativeMethods.VirtualQuery( + lpAddress: _handle.DangerousGetHandle() + _byteOffsetIntoHandle, + lpBuffer: out MEMORY_BASIC_INFORMATION memoryInfo, + dwLength: (IntPtr)sizeof(MEMORY_BASIC_INFORMATION)) == IntPtr.Zero) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + throw new InvalidOperationException("VirtualQuery failed unexpectedly."); + } + return memoryInfo.Protect; + } + finally + { + if (refAdded) + { + _handle.DangerousRelease(); + } + } + } + set + { + if (_elementCount > 0) + { + bool refAdded = false; + try + { + _handle.DangerousAddRef(ref refAdded); + if (!UnsafeNativeMethods.VirtualProtect( + lpAddress: _handle.DangerousGetHandle() + _byteOffsetIntoHandle, + dwSize: (IntPtr)(&((T*)null)[_elementCount]), + flNewProtect: value, + lpflOldProtect: out _)) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + throw new InvalidOperationException("VirtualProtect failed unexpectedly."); + } + } + finally + { + if (refAdded) + { + _handle.DangerousRelease(); + } + } + } + } + } + + public override Memory Memory => _memoryManager.Memory; + + public override Span Span + { + get + { + bool refAdded = false; + try + { + _handle.DangerousAddRef(ref refAdded); + return new Span((void*)(_handle.DangerousGetHandle() + _byteOffsetIntoHandle), _elementCount); + } + finally + { + if (refAdded) + { + _handle.DangerousRelease(); + } + } + } + } + + public override void Dispose() + { + _handle.Dispose(); + } + + public override void MakeReadonly() + { + Protection = VirtualAllocProtection.PAGE_READONLY; + } + + public override void MakeWriteable() + { + Protection = VirtualAllocProtection.PAGE_READWRITE; + } + + private sealed class BoundedMemoryManager : MemoryManager + { + private readonly WindowsImplementation _impl; + + public BoundedMemoryManager(WindowsImplementation impl) + { + _impl = impl; + } + + public override Memory Memory => CreateMemory(_impl._elementCount); + + protected override void Dispose(bool disposing) + { + // no-op; the handle will be disposed separately + } + + public override Span GetSpan() => _impl.Span; + + public override MemoryHandle Pin(int elementIndex) + { + if ((uint)elementIndex > (uint)_impl._elementCount) + { + throw new ArgumentOutOfRangeException(paramName: nameof(elementIndex)); + } + + bool refAdded = false; + try + { + _impl._handle.DangerousAddRef(ref refAdded); + return new MemoryHandle((T*)(_impl._handle.DangerousGetHandle() + _impl._byteOffsetIntoHandle) + elementIndex); + } + finally + { + if (refAdded) + { + _impl._handle.DangerousRelease(); + } + } + } + + public override void Unpin() + { + // no-op - we don't unpin native memory + } + } + } + + // from winnt.h + [Flags] + private enum VirtualAllocAllocationType : uint + { + MEM_COMMIT = 0x1000, + MEM_RESERVE = 0x2000, + MEM_DECOMMIT = 0x4000, + MEM_RELEASE = 0x8000, + MEM_FREE = 0x10000, + MEM_PRIVATE = 0x20000, + MEM_MAPPED = 0x40000, + MEM_RESET = 0x80000, + MEM_TOP_DOWN = 0x100000, + MEM_WRITE_WATCH = 0x200000, + MEM_PHYSICAL = 0x400000, + MEM_ROTATE = 0x800000, + MEM_LARGE_PAGES = 0x20000000, + MEM_4MB_PAGES = 0x80000000, + } + + // from winnt.h + [Flags] + private enum VirtualAllocProtection : uint + { + PAGE_NOACCESS = 0x01, + PAGE_READONLY = 0x02, + PAGE_READWRITE = 0x04, + PAGE_WRITECOPY = 0x08, + PAGE_EXECUTE = 0x10, + PAGE_EXECUTE_READ = 0x20, + PAGE_EXECUTE_READWRITE = 0x40, + PAGE_EXECUTE_WRITECOPY = 0x80, + PAGE_GUARD = 0x100, + PAGE_NOCACHE = 0x200, + PAGE_WRITECOMBINE = 0x400, + } + + [StructLayout(LayoutKind.Sequential)] + private struct MEMORY_BASIC_INFORMATION + { + public IntPtr BaseAddress; + public IntPtr AllocationBase; + public VirtualAllocProtection AllocationProtect; + public IntPtr RegionSize; + public VirtualAllocAllocationType State; + public VirtualAllocProtection Protect; + public VirtualAllocAllocationType Type; + }; + + private sealed class VirtualAllocHandle : SafeHandle + { + // Called by P/Invoke when returning SafeHandles + public VirtualAllocHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + // Do not provide a finalizer - SafeHandle's critical finalizer will + // call ReleaseHandle for you. + + public override bool IsInvalid => (handle == IntPtr.Zero); + + protected override bool ReleaseHandle() => + UnsafeNativeMethods.VirtualFree(handle, IntPtr.Zero, VirtualAllocAllocationType.MEM_RELEASE); + } + + private static partial class UnsafeNativeMethods + { + private const string KERNEL32_LIB = "kernel32.dll"; + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366887(v=vs.85).aspx + [DllImport(KERNEL32_LIB, SetLastError = true)] + public static extern VirtualAllocHandle VirtualAlloc( + IntPtr lpAddress, + IntPtr dwSize, + VirtualAllocAllocationType flAllocationType, + VirtualAllocProtection flProtect); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366892(v=vs.85).aspx + [DllImport(KERNEL32_LIB, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool VirtualFree( + IntPtr lpAddress, + IntPtr dwSize, + VirtualAllocAllocationType dwFreeType); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366898(v=vs.85).aspx + [DllImport(KERNEL32_LIB, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool VirtualProtect( + IntPtr lpAddress, + IntPtr dwSize, + VirtualAllocProtection flNewProtect, + out VirtualAllocProtection lpflOldProtect); + + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366902(v=vs.85).aspx + [DllImport(KERNEL32_LIB, SetLastError = true)] + public static extern IntPtr VirtualQuery( + IntPtr lpAddress, + out MEMORY_BASIC_INFORMATION lpBuffer, + IntPtr dwLength); + } + } +} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.cs new file mode 100644 index 00000000000000..81d359cc80e926 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Buffers +{ + /// + /// Represents a region of native memory. The property can be used + /// to get a backed by this memory region. + /// + public abstract class BoundedMemory : IDisposable where T : unmanaged + { + /// + /// Returns a value stating whether this native memory block is readonly. + /// + public abstract bool IsReadonly { get; } + + /// Gets the length of the instance. + public abstract int Length { get; } + + /// + /// Gets the which represents this native memory. + /// This instance must be kept alive while working with the . + /// + public abstract Memory Memory { get; } + + /// + /// Gets the which represents this native memory. + /// This instance must be kept alive while working with the . + /// + public abstract Span Span { get; } + + /// + /// Disposes this instance. + /// + public abstract void Dispose(); + + /// + /// Sets this native memory block to be readonly. Writes to this block will cause an AV. + /// This method has no effect if the memory block is zero length or if the underlying + /// OS does not support marking the memory block as readonly. + /// + public abstract void MakeReadonly(); + + /// + /// Sets this native memory block to be read+write. + /// This method has no effect if the memory block is zero length or if the underlying + /// OS does not support marking the memory block as read+write. + /// + public abstract void MakeWriteable(); + + /// + /// Gets the which represents this native memory. + /// This instance must be kept alive while working with the . + /// + public static implicit operator Span(BoundedMemory boundedMemory) => boundedMemory.Span; + + /// + /// Gets the which represents this native memory. + /// This instance must be kept alive while working with the . + /// + public static implicit operator ReadOnlySpan(BoundedMemory boundedMemory) => boundedMemory.Span; + + /// + /// Gets a reference to the element at the specified index. + /// This instance must be kept alive while working with the reference. + /// + public ref T this[int index] => ref Span[index]; + } +} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PoisonPagePlacement.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PoisonPagePlacement.cs new file mode 100644 index 00000000000000..dfbffbd99e00b3 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PoisonPagePlacement.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Buffers +{ + /// + /// Dictates where the poison page should be placed. + /// + public enum PoisonPagePlacement + { + /// + /// The poison page should be placed immediately after the memory region. + /// Attempting to access the memory page immediately following the + /// span will result in an AV. + /// + After, + + /// + /// The poison page should be placed immediately before the memory region. + /// Attempting to access the memory page immediately before the + /// span will result in an AV. + /// + Before, + } +} \ No newline at end of file diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PooledBoundedMemory.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PooledBoundedMemory.cs new file mode 100644 index 00000000000000..320f3acfe927d3 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PooledBoundedMemory.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Threading; + +namespace DotnetFuzzing +{ + internal sealed class PooledBoundedMemory : IDisposable where T : unmanaged + { + // Default libFuzzer max_len for inputs is 4096. + private const int MaxLength = 4096 * 2; + + private static readonly PooledBoundedMemory[] s_memoryWithPoisonBefore = new PooledBoundedMemory[MaxLength + 1]; + private static readonly PooledBoundedMemory[] s_memoryWithPoisonAfter = new PooledBoundedMemory[MaxLength + 1]; + + private readonly BoundedMemory _memory; + private readonly PooledBoundedMemory[] _pool; + + private PooledBoundedMemory(PooledBoundedMemory[] pool, int elementCount, PoisonPagePlacement placement) + { + _pool = pool; + _memory = BoundedMemory.Allocate(elementCount, placement); + } + + public BoundedMemory InnerMemory => _memory; + public Memory Memory => _memory.Memory; + public Span Span => _memory.Span; + + public void Dispose() + { + if (_pool is null || + Interlocked.CompareExchange(ref _pool[_memory.Length], this, null) != null) + { + _memory.Dispose(); + } + } + + public static PooledBoundedMemory Rent(int elementCount, PoisonPagePlacement placement) + { + if ((uint)elementCount >= MaxLength) + { + return new PooledBoundedMemory(null, elementCount, placement); + } + + PooledBoundedMemory[] pool = null; + switch (placement) + { + case PoisonPagePlacement.Before: + pool = s_memoryWithPoisonBefore; + break; + case PoisonPagePlacement.After: + pool = s_memoryWithPoisonAfter; + break; + default: + throw new ArgumentOutOfRangeException(nameof(placement)); + } + + return + Interlocked.Exchange(ref pool[elementCount], null) ?? + new PooledBoundedMemory(pool, elementCount, placement); + } + + public static PooledBoundedMemory Rent(ReadOnlySpan data, PoisonPagePlacement placement) + { + PooledBoundedMemory memory = Rent(data.Length, placement); + data.CopyTo(memory.Span); + return memory; + } + } +} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncoding.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncoding.cs new file mode 100644 index 00000000000000..37d61c86f7a8ee --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncoding.cs @@ -0,0 +1,212 @@ +// 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.Text; +using System; + +namespace DotnetFuzzing.Fuzzers +{ + internal sealed class TextEncoding + { + static void Main(string[] args) + { + if (args.Length != 1) + { + Console.WriteLine("Usage: TextEncoding "); + return; + } + + byte[] bytes = Convert.FromBase64String(args[0]); + + TextEncoding textEncoding = new TextEncoding(); + textEncoding.FuzzTarget(bytes); + } + + public void FuzzTarget(ReadOnlySpan bytes) + { + using (PooledBoundedMemory poisonAfter = PooledBoundedMemory.Rent(bytes, PoisonPagePlacement.After)) + { + TestLatin1(poisonAfter.Span); + TestASCII(poisonAfter.Span); + TestUnicode(poisonAfter.Span); + TestUtf32(poisonAfter.Span); + TestUtf7(poisonAfter.Span); + TestUtf8(poisonAfter.Span); + } + } + + // Use individual methods for each encoding, so if there's an exception then + // it's clear which encoding failed based on the call stack. + + private static void TestLatin1(ReadOnlySpan input) + { + TestWithSubstitution(input, Encoding.GetEncoding("ISO-8859-1")); + TestWithConvert(input, Encoding.GetEncoding("ISO-8859-1")); + } + + private static void TestASCII(ReadOnlySpan input) + { + TestWithSubstitution(input, new ASCIIEncoding()); + TestWithConvert(input, new ASCIIEncoding()); + } + + private static void TestUnicode(ReadOnlySpan input) + { + TestWithSubstitution(input, new UnicodeEncoding()); + TestWithExceptions(input, new UnicodeEncoding(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: true)); + TestWithConvert(input, new UnicodeEncoding()); + } + + private static void TestUtf32(ReadOnlySpan input) + { + TestWithSubstitution(input, new UTF32Encoding()); + TestWithExceptions(input, new UTF32Encoding(bigEndian: false, byteOrderMark: false, throwOnInvalidCharacters: true)); + TestWithConvert(input, new UTF32Encoding()); + } + + private static void TestUtf7(ReadOnlySpan input) + { +#pragma warning disable SYSLIB0001 // Type or member is obsolete + TestWithSubstitution(input, new UTF7Encoding()); +#pragma warning restore SYSLIB0001 + } + + private static void TestUtf8(ReadOnlySpan input) + { + TestWithSubstitution(input, new UTF8Encoding()); + TestWithExceptions(input, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true)); + TestWithConvert(input, new UTF8Encoding()); + } + + unsafe private static void TestWithSubstitution(ReadOnlySpan input, Encoding encoding) + { + Decoder decoder = encoding.GetDecoder(); + + fixed (byte* pInput = input) + { + int charCount = encoding.GetCharCount(pInput, input.Length); + + using (PooledBoundedMemory chars = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After)) + using (PooledBoundedMemory chars2 = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After)) + using (PooledBoundedMemory bytes = PooledBoundedMemory.Rent(charCount * 4 + 2, PoisonPagePlacement.After)) + fixed (char* pchars = chars.Span) + fixed (char* pchars2 = chars2.Span) + fixed (byte* pbytes = bytes.Span) + { + if (chars.Span.Length == 0) + { + return; + } + + decoder.Reset(); + int written = decoder.GetChars(pInput, input.Length, pchars, chars.Span.Length, flush: true); + Assert.Equal(charCount, written); + + Encoder encoder = encoding.GetEncoder(); + int bytesWritten = encoder.GetBytes(pchars, chars.Span.Length, pbytes, bytes.Span.Length, flush: true); + + // Decode the encoded values. Any substitutions will be comparable now. + decoder.Reset(); + written = decoder.GetChars(pbytes, bytesWritten, pchars2, chars2.Span.Length, flush: true); + Assert.Equal(charCount, written); + + // Verify that we round-tripped the values. + Assert.SequenceEqual(chars.Span, chars2.Span); + } + } + } + + // If there are substitutions, these cases will fail with DecoderFallbackException early on, + // otherwise there should be no DecoderFallbackExceptions. + unsafe private static void TestWithExceptions(ReadOnlySpan input, Encoding encoding) + { + Assert.Equal(typeof(DecoderExceptionFallback), encoding.DecoderFallback.GetType()); + Assert.Equal(typeof(EncoderExceptionFallback), encoding.EncoderFallback.GetType()); + + Decoder decoder = encoding.GetDecoder(); + + int charCount; + try + { + fixed (byte* pinput = input) + { + charCount = decoder.GetCharCount(pinput, input.Length, flush: true); + } + } + catch (DecoderFallbackException) + { + // The input is not valid without fallbacks. + return; + } + + TestWithSubstitution(input, encoding); + } + + private static void TestWithConvert(ReadOnlySpan input, Encoding encoding) + { + // Use a few boundary cases. + TestWithConvert(input, encoding, 1); + TestWithConvert(input, encoding, 2); + TestWithConvert(input, encoding, 3); + TestWithConvert(input, encoding, 4); + TestWithConvert(input, encoding, input.Length); + + if (input.Length >= 6) + { + TestWithConvert(input, encoding, input.Length - 1); + + if (input.Length >= 12) + { + TestWithConvert(input, encoding, input.Length / 2); + } + } + } + + // Verify that obtaining data using several Convert() calls matches the result from a single GetChars() call. + unsafe private static void TestWithConvert(ReadOnlySpan input, Encoding encoding, int blockSize) + { + Decoder decoder = encoding.GetDecoder(); + Encoder encoder = encoding.GetEncoder(); + + fixed (byte* pinput = input) + { + int charCount = decoder.GetCharCount(pinput, input.Length, flush: true); + + using (PooledBoundedMemory chars = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After)) + using (PooledBoundedMemory chars2 = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After)) + fixed (char* pchars = chars.Span) + fixed (char* pchars2 = chars2.Span) + { + decoder.Reset(); + int charsUsedTotal = 0; + int i = 0; + + while (i < input.Length) + { + bool lastIteration = i + blockSize >= input.Length; + int bytesToRead = lastIteration ? input.Length - i : blockSize; + + decoder.Convert( + bytes: pinput + i, + byteCount: bytesToRead, + chars: pchars + charsUsedTotal, + charCount: charCount - charsUsedTotal, + flush: lastIteration, + out int bytesUsed, + out int charsUsed, + out bool _); + + i += bytesUsed; + charsUsedTotal += charsUsed; + } + + Assert.Equal(charsUsedTotal, charCount); + decoder.Reset(); + decoder.GetChars(pinput, input.Length, pchars2, chars2.Span.Length, flush: true); + Assert.SequenceEqual(chars.Span, chars2.Span); + } + } + } + } +} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.csproj b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.csproj new file mode 100644 index 00000000000000..a1d11b067b3e69 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A} + Exe + TextEncodingNetFramework + TextEncodingNetFramework + v4.8.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/packages.config b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/packages.config new file mode 100644 index 00000000000000..bd17b9f57d3d8c --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs new file mode 100644 index 00000000000000..9e7db84980c79b --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs @@ -0,0 +1,178 @@ +// 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.Text; + +namespace DotnetFuzzing.Fuzzers; + +internal sealed class TextEncoding : IFuzzer +{ + string[] IFuzzer.TargetAssemblies => []; + string[] IFuzzer.TargetCoreLibPrefixes { get; } = ["System.Text"]; + + void IFuzzer.FuzzTarget(ReadOnlySpan bytes) + { + using PooledBoundedMemory poisonAfter = PooledBoundedMemory.Rent(bytes, PoisonPagePlacement.After); + + TestLatin1(poisonAfter.Span); + TestASCII(poisonAfter.Span); + TestUnicode(poisonAfter.Span); + TestUtf32(poisonAfter.Span); + TestUtf7(poisonAfter.Span); + TestUtf8(poisonAfter.Span); + } + + // Use individual methods for each encoding, so if there's an exception then + // it's clear which encoding failed based on the call stack. + + private static void TestLatin1(ReadOnlySpan input) + { + TestWithSubstitution(input, Encoding.GetEncoding("ISO-8859-1")); + TestWithConvert(input, Encoding.GetEncoding("ISO-8859-1")); + } + + private static void TestASCII(ReadOnlySpan input) + { + TestWithSubstitution(input, new ASCIIEncoding()); + TestWithConvert(input, new ASCIIEncoding()); + } + + private static void TestUnicode(ReadOnlySpan input) + { + TestWithSubstitution(input, new UnicodeEncoding()); + TestWithExceptions(input, new UnicodeEncoding(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: true)); + TestWithConvert(input, new UnicodeEncoding()); + } + + private static void TestUtf32(ReadOnlySpan input) + { + TestWithSubstitution(input, new UTF32Encoding()); + TestWithExceptions(input, new UTF32Encoding(bigEndian: false, byteOrderMark: false, throwOnInvalidCharacters: true)); + TestWithConvert(input, new UTF32Encoding()); + } + + private static void TestUtf7(ReadOnlySpan input) + { +#pragma warning disable SYSLIB0001 // Type or member is obsolete + TestWithSubstitution(input, new UTF7Encoding()); +#pragma warning restore SYSLIB0001 + } + + private static void TestUtf8(ReadOnlySpan input) + { + TestWithSubstitution(input, new UTF8Encoding()); + TestWithExceptions(input, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true)); + TestWithConvert(input, new UTF8Encoding()); + } + + private static void TestWithSubstitution(ReadOnlySpan input, Encoding encoding) + { + Decoder decoder = encoding.GetDecoder(); + int charCount = decoder.GetCharCount(input, flush: true); + + using PooledBoundedMemory chars = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After); + using PooledBoundedMemory chars2 = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After); + + // *4 for worst case scenario (*2 for char->byte + *2 for encoding) + // +2 is for possible Base64 padding with UTF7Encoding. + using PooledBoundedMemory bytes = PooledBoundedMemory.Rent(charCount * 4 + 2, PoisonPagePlacement.After); + + decoder.Reset(); + int written = decoder.GetChars(input, chars.Span, flush: true); + Assert.Equal(charCount, written); + + Encoder encoder = encoding.GetEncoder(); + // We use flush:true here for UTF7Encoding which may do Base64 padding at the end. + int bytesWritten = encoder.GetBytes(chars.Span, bytes.Span, flush: true); + + // Decode the encoded values. Any substitutions will be comparable now. + decoder.Reset(); + written = decoder.GetChars(bytes.Span.Slice(0, bytesWritten), chars2.Span, flush: true); + Assert.Equal(charCount, written); + + // Verify that we round-tripped the values. + Assert.SequenceEqual(chars.Span, chars2.Span); + } + + // If there are substitutions, these cases will fail with DecoderFallbackException early on, + // otherwise there should be no DecoderFallbackExceptions. + private static void TestWithExceptions(ReadOnlySpan input, Encoding encoding) + { + Assert.Equal(typeof(DecoderExceptionFallback), encoding.DecoderFallback.GetType()); + Assert.Equal(typeof(EncoderExceptionFallback), encoding.EncoderFallback.GetType()); + + Decoder decoder = encoding.GetDecoder(); + + int charCount; + try + { + charCount = decoder.GetCharCount(input, flush: true); + } + catch (DecoderFallbackException) + { + // The input is not valid without fallbacks. + return; + } + + TestWithSubstitution(input, encoding); + } + + private static void TestWithConvert(ReadOnlySpan input, Encoding encoding) + { + // Use a few boundary cases. + TestWithConvert(input, encoding, 1); + TestWithConvert(input, encoding, 2); + TestWithConvert(input, encoding, 3); + TestWithConvert(input, encoding, 4); + TestWithConvert(input, encoding, input.Length); + + if (input.Length >= 6) + { + TestWithConvert(input, encoding, input.Length - 1); + + if (input.Length >= 12) + { + TestWithConvert(input, encoding, input.Length / 2); + } + } + } + + // Verify that obtaining data using several Convert() calls matches the result from a single GetChars() call. + private static void TestWithConvert(ReadOnlySpan input, Encoding encoding, int blockSize) + { + Decoder decoder = encoding.GetDecoder(); + Encoder encoder = encoding.GetEncoder(); + + int charCount = decoder.GetCharCount(input, flush: true); + + using PooledBoundedMemory chars = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After); + using PooledBoundedMemory chars2 = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After); + + decoder.Reset(); + int charsUsedTotal = 0; + int i = 0; + + while (i < input.Length) + { + bool lastIteration = i + blockSize >= input.Length; + int bytesToRead = lastIteration ? input.Length - i : blockSize; + + decoder.Convert( + input.Slice(i, bytesToRead), + chars.Span.Slice(charsUsedTotal, charCount - charsUsedTotal), + flush: lastIteration, + out int bytesUsed, + out int charsUsed, + out bool _); + + i += bytesUsed; + charsUsedTotal += charsUsed; + } + + Assert.Equal(charsUsedTotal, charCount); + decoder.Reset(); + decoder.GetChars(input, chars2.Span, flush: true); + Assert.SequenceEqual(chars.Span, chars2.Span); + } +} From 8c4504902c19a76fb57710ede768f36f3b3f57f9 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 24 Jun 2024 09:26:00 -0500 Subject: [PATCH 2/6] Update netfx --- .../DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs | 7 +++++-- .../Fuzzers/TextEncoding.NetFramework/Assert.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs index d3a017ec1ec9a2..650a291ab50dd1 100644 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs @@ -21,8 +21,11 @@ void IFuzzer.FuzzTarget(ReadOnlySpan bytes) Console.WriteLine($"Cannot find the .NET Framework test executable at {path}"); } - string encoded = Convert.ToBase64String(bytes); - Process.Start(path, encoded); + if (bytes.Length > 0) + { + string encoded = Convert.ToBase64String(bytes); + Process.Start(path, encoded); + } } private static string GetRepoRootDirectory() diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs index 51b80c20380594..5bd6f1c8f0f703 100644 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs @@ -22,7 +22,7 @@ public static void SequenceEqual(ReadOnlySpan expected, ReadOnlySpan { Equal(expected.Length, actual.Length); int diffIndex = expected.CommonPrefixLength(actual); - throw new Exception($"Expected={expected[diffIndex]} Actual={actual[diffIndex]} at index {diffIndex}"); + throw new Exception($"Expected={expected[diffIndex]} 0x{(byte)expected[diffIndex]} Actual={actual[diffIndex]} 0x{(byte)actual[diffIndex]} at index {diffIndex}"); } } From 94010d8edda6cf4d12bcde4ee7fac440c33c1f21 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 25 Jun 2024 09:17:29 -0500 Subject: [PATCH 3/6] Forward to .NET Framework when Core fuzzing --- .../TextEncodingNetFramework.sln | 25 ++++++++++ .../DotnetFuzzing/Fuzzers/TextEncoding.cs | 46 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.sln diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.sln b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.sln new file mode 100644 index 00000000000000..b0c0ae2c0ee6c9 --- /dev/null +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.34929.205 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextEncodingNetFramework", "TextEncodingNetFramework.csproj", "{1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7473A937-4769-400E-A417-BD1856CFD3B5} + EndGlobalSection +EndGlobal diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs index 9e7db84980c79b..8ccf6db1814e96 100644 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs @@ -1,13 +1,44 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#define FORWARD_TO_NETFRAMEWORK + using System.Buffers; +using System.Diagnostics; using System.Text; namespace DotnetFuzzing.Fuzzers; internal sealed class TextEncoding : IFuzzer { +#if FORWARD_TO_NETFRAMEWORK + const string TestSubDirectory = @"src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\TextEncoding.NetFramework\bin\Release\TextEncodingNetFramework.exe"; + //string[] IFuzzer.TargetAssemblies => ["mscorlib"]; // Not sure if this does anything. + //string[] IFuzzer.TargetCoreLibPrefixes { get; } = []; + + private static string GetRepoRootDirectory() + { + string? currentDirectory = Directory.GetCurrentDirectory(); + + while (currentDirectory != null) + { + string gitDirOrFile = Path.Combine(currentDirectory, ".git"); + if (Directory.Exists(gitDirOrFile) || File.Exists(gitDirOrFile)) + { + break; + } + currentDirectory = Directory.GetParent(currentDirectory)?.FullName; + } + + if (currentDirectory == null) + { + throw new Exception("Cannot find the git repository root"); + } + + return currentDirectory; + } +#endif + string[] IFuzzer.TargetAssemblies => []; string[] IFuzzer.TargetCoreLibPrefixes { get; } = ["System.Text"]; @@ -21,6 +52,21 @@ void IFuzzer.FuzzTarget(ReadOnlySpan bytes) TestUtf32(poisonAfter.Span); TestUtf7(poisonAfter.Span); TestUtf8(poisonAfter.Span); + +#if FORWARD_TO_NETFRAMEWORK + string path = GetRepoRootDirectory(); + path = Path.Combine(path, TestSubDirectory); + if (!File.Exists(path)) + { + Console.WriteLine($"Cannot find the .NET Framework test executable at {path}"); + } + + if (bytes.Length > 0) + { + string encoded = Convert.ToBase64String(bytes); + Process.Start(path, encoded); + } +#endif } // Use individual methods for each encoding, so if there's an exception then From 003e433e0c975eab616a5a905662085c7631952e Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 25 Jun 2024 09:52:32 -0500 Subject: [PATCH 4/6] Remove .NET Framework forwarding approach; approach preserved in this commit --- .../libraries/fuzzing/deploy-to-onefuzz.yml | 12 +- .../Fuzzers/TextEncoding.NetFramework.cs | 52 --- .../TextEncoding.NetFramework/App.config | 6 - .../TextEncoding.NetFramework/Assert.cs | 44 --- .../BoundedMemory.Creation.cs | 84 ----- .../BoundedMemory.Windows.cs | 328 ------------------ .../BoundedMemory.cs | 69 ---- .../PoisonPagePlacement.cs | 25 -- .../PooledBoundedMemory.cs | 72 ---- .../TextEncoding.NetFramework/TextEncoding.cs | 212 ----------- .../TextEncodingNetFramework.csproj | 74 ---- .../TextEncodingNetFramework.sln | 25 -- .../TextEncoding.NetFramework/packages.config | 7 - ...{TextEncoding.cs => TextEncodingFuzzer.cs} | 57 +-- 14 files changed, 14 insertions(+), 1053 deletions(-) delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/App.config delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Creation.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Windows.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PoisonPagePlacement.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PooledBoundedMemory.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncoding.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.csproj delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.sln delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/packages.config rename src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/{TextEncoding.cs => TextEncodingFuzzer.cs} (81%) diff --git a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml index 00d63f36df593d..c08d613249c82b 100644 --- a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml +++ b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml @@ -101,17 +101,9 @@ extends: inputs: onefuzzOSes: 'Windows' env: - onefuzzDropDirectory: $(fuzzerProject)/deployment/TextEncoding + onefuzzDropDirectory: $(fuzzerProject)/deployment/TextEncodingFuzzer SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: Send TextEncoding to OneFuzz - - - task: onefuzz-task@0 - inputs: - onefuzzOSes: 'Windows' - env: - onefuzzDropDirectory: $(fuzzerProject)/deployment/TextEncodingNetFramework - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: Send TextEncodingNetFramework to OneFuzz + displayName: Send TextEncodingFuzzer to OneFuzz - task: onefuzz-task@0 inputs: diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs deleted file mode 100644 index 650a291ab50dd1..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; - -namespace DotnetFuzzing.Fuzzers; - -internal sealed class TextEncodingNetFramework : IFuzzer -{ - const string TestSubDirectory = @"src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\TextEncoding.NetFramework\bin\Release\TextEncodingNetFramework.exe"; - - string[] IFuzzer.TargetAssemblies => ["mscorlib"]; // Not sure if this does anything. - string[] IFuzzer.TargetCoreLibPrefixes { get; } = []; - - void IFuzzer.FuzzTarget(ReadOnlySpan bytes) - { - string path = GetRepoRootDirectory(); - path = Path.Combine(path, TestSubDirectory); - if (!File.Exists(path)) - { - Console.WriteLine($"Cannot find the .NET Framework test executable at {path}"); - } - - if (bytes.Length > 0) - { - string encoded = Convert.ToBase64String(bytes); - Process.Start(path, encoded); - } - } - - private static string GetRepoRootDirectory() - { - string? currentDirectory = Directory.GetCurrentDirectory(); - - while (currentDirectory != null) - { - string gitDirOrFile = Path.Combine(currentDirectory, ".git"); - if (Directory.Exists(gitDirOrFile) || File.Exists(gitDirOrFile)) - { - break; - } - currentDirectory = Directory.GetParent(currentDirectory)?.FullName; - } - - if (currentDirectory == null) - { - throw new Exception("Cannot find the git repository root"); - } - - return currentDirectory; - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/App.config b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/App.config deleted file mode 100644 index aee9adf485c918..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs deleted file mode 100644 index 5bd6f1c8f0f703..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/Assert.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; - -namespace DotnetFuzzing -{ - internal static class Assert - { - public static void Equal(T expected, T actual) - { - if (!EqualityComparer.Default.Equals(expected, actual)) - { - throw new Exception($"Expected={expected} Actual={actual}"); - } - } - - public static void SequenceEqual(ReadOnlySpan expected, ReadOnlySpan actual) - { - if (!expected.SequenceEqual(actual)) - { - Equal(expected.Length, actual.Length); - int diffIndex = expected.CommonPrefixLength(actual); - throw new Exception($"Expected={expected[diffIndex]} 0x{(byte)expected[diffIndex]} Actual={actual[diffIndex]} 0x{(byte)actual[diffIndex]} at index {diffIndex}"); - } - } - - public static int CommonPrefixLength(this ReadOnlySpan span, ReadOnlySpan other) - { - int length = Math.Min(span.Length, other.Length); - - for (int i = 0; i < length; i++) - { - if (span[i] != other[i]) - { - return i; - } - } - - return length; - } - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Creation.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Creation.cs deleted file mode 100644 index 92896510e29103..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Creation.cs +++ /dev/null @@ -1,84 +0,0 @@ -// 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.InteropServices; - -namespace System.Buffers -{ - /// - /// Contains factory methods to create instances. - /// - public static partial class BoundedMemory - { - /// - /// Allocates a new region which is immediately preceded by - /// or immediately followed by a poison (MEM_NOACCESS) page. If - /// is , then attempting to read the memory - /// immediately before the returned will result in an AV. - /// If is , then - /// attempting to read the memory immediately after the returned - /// will result in AV. - /// - /// - /// The newly-allocated memory will be populated with random data. - /// - public static BoundedMemory Allocate(int elementCount, PoisonPagePlacement placement = PoisonPagePlacement.After) where T : unmanaged - { - if (elementCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(elementCount)); - } - if (placement != PoisonPagePlacement.Before && placement != PoisonPagePlacement.After) - { - throw new ArgumentOutOfRangeException(nameof(placement)); - } - - BoundedMemory retVal = AllocateWithoutDataPopulation(elementCount, placement); - FillRandom(MemoryMarshal.AsBytes(retVal.Span)); - return retVal; - } - - /// - /// Similar to , but populates the allocated - /// native memory block from existing data rather than using random data. - /// - public static BoundedMemory AllocateFromExistingData(ReadOnlySpan data, PoisonPagePlacement placement = PoisonPagePlacement.After) where T : unmanaged - { - if (placement != PoisonPagePlacement.Before && placement != PoisonPagePlacement.After) - { - throw new ArgumentOutOfRangeException(nameof(placement)); - } - - BoundedMemory retVal = AllocateWithoutDataPopulation(data.Length, placement); - data.CopyTo(retVal.Span); - return retVal; - } - - /// - /// Similar to , but populates the allocated - /// native memory block from existing data rather than using random data. - /// - public static BoundedMemory AllocateFromExistingData(T[] data, PoisonPagePlacement placement = PoisonPagePlacement.After) where T : unmanaged - { - return AllocateFromExistingData(new ReadOnlySpan(data), placement); - } - - private static void FillRandom(Span buffer) - { - // Loop over a Random instance manually since Random.NextBytes(Span) doesn't - // exist on all platforms we target. - - Random random = new Random(); // doesn't need to be cryptographically strong - - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = (byte)random.Next(); - } - } - - private static BoundedMemory AllocateWithoutDataPopulation(int elementCount, PoisonPagePlacement placement) where T : unmanaged - { - return AllocateWithoutDataPopulationWindows(elementCount, placement); - } - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Windows.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Windows.cs deleted file mode 100644 index 023566af11a936..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.Windows.cs +++ /dev/null @@ -1,328 +0,0 @@ -// 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.InteropServices; - -namespace System.Buffers -{ - public static unsafe partial class BoundedMemory - { - private static readonly int SystemPageSize = Environment.SystemPageSize; - - private static WindowsImplementation AllocateWithoutDataPopulationWindows(int elementCount, PoisonPagePlacement placement) where T : unmanaged - { - long cb, totalBytesToAllocate; - checked - { - cb = elementCount * sizeof(T); - totalBytesToAllocate = cb; - - // We only need to round the count up if it's not an exact multiple - // of the system page size. - - long leftoverBytes = totalBytesToAllocate % SystemPageSize; - if (leftoverBytes != 0) - { - totalBytesToAllocate += SystemPageSize - leftoverBytes; - } - - // Finally, account for the poison pages at the front and back. - - totalBytesToAllocate += 2 * SystemPageSize; - } - - // Reserve and commit the entire range as NOACCESS. - - VirtualAllocHandle handle = UnsafeNativeMethods.VirtualAlloc( - lpAddress: IntPtr.Zero, - dwSize: (IntPtr)totalBytesToAllocate /* cast throws OverflowException if out of range */, - flAllocationType: VirtualAllocAllocationType.MEM_RESERVE | VirtualAllocAllocationType.MEM_COMMIT, - flProtect: VirtualAllocProtection.PAGE_NOACCESS); - - if (handle == null || handle.IsInvalid) - { - int lastError = Marshal.GetHRForLastWin32Error(); - handle?.Dispose(); - Marshal.ThrowExceptionForHR(lastError); - throw new InvalidOperationException("VirtualAlloc failed unexpectedly."); - } - - // Done allocating! Now carve out a READWRITE section bookended by the NOACCESS - // pages and return that carved-out section to the caller. Since memory protection - // flags only apply at page-level granularity, we need to "left-align" or "right- - // align" the section we carve out so that it's guaranteed adjacent to one of - // the NOACCESS bookend pages. - - return new WindowsImplementation( - handle: handle, - byteOffsetIntoHandle: (placement == PoisonPagePlacement.Before) - ? SystemPageSize /* just after leading poison page */ - : checked((int)(totalBytesToAllocate - SystemPageSize - cb)) /* just before trailing poison page */, - elementCount: elementCount) - { - Protection = VirtualAllocProtection.PAGE_READWRITE - }; - } - - private sealed class WindowsImplementation : BoundedMemory where T : unmanaged - { - private readonly VirtualAllocHandle _handle; - private readonly int _byteOffsetIntoHandle; - private readonly int _elementCount; - private readonly BoundedMemoryManager _memoryManager; - - internal WindowsImplementation(VirtualAllocHandle handle, int byteOffsetIntoHandle, int elementCount) - { - _handle = handle; - _byteOffsetIntoHandle = byteOffsetIntoHandle; - _elementCount = elementCount; - _memoryManager = new BoundedMemoryManager(this); - } - - public override bool IsReadonly => (Protection != VirtualAllocProtection.PAGE_READWRITE); - - public override int Length => _elementCount; - - internal VirtualAllocProtection Protection - { - get - { - bool refAdded = false; - try - { - _handle.DangerousAddRef(ref refAdded); - if (UnsafeNativeMethods.VirtualQuery( - lpAddress: _handle.DangerousGetHandle() + _byteOffsetIntoHandle, - lpBuffer: out MEMORY_BASIC_INFORMATION memoryInfo, - dwLength: (IntPtr)sizeof(MEMORY_BASIC_INFORMATION)) == IntPtr.Zero) - { - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); - throw new InvalidOperationException("VirtualQuery failed unexpectedly."); - } - return memoryInfo.Protect; - } - finally - { - if (refAdded) - { - _handle.DangerousRelease(); - } - } - } - set - { - if (_elementCount > 0) - { - bool refAdded = false; - try - { - _handle.DangerousAddRef(ref refAdded); - if (!UnsafeNativeMethods.VirtualProtect( - lpAddress: _handle.DangerousGetHandle() + _byteOffsetIntoHandle, - dwSize: (IntPtr)(&((T*)null)[_elementCount]), - flNewProtect: value, - lpflOldProtect: out _)) - { - Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); - throw new InvalidOperationException("VirtualProtect failed unexpectedly."); - } - } - finally - { - if (refAdded) - { - _handle.DangerousRelease(); - } - } - } - } - } - - public override Memory Memory => _memoryManager.Memory; - - public override Span Span - { - get - { - bool refAdded = false; - try - { - _handle.DangerousAddRef(ref refAdded); - return new Span((void*)(_handle.DangerousGetHandle() + _byteOffsetIntoHandle), _elementCount); - } - finally - { - if (refAdded) - { - _handle.DangerousRelease(); - } - } - } - } - - public override void Dispose() - { - _handle.Dispose(); - } - - public override void MakeReadonly() - { - Protection = VirtualAllocProtection.PAGE_READONLY; - } - - public override void MakeWriteable() - { - Protection = VirtualAllocProtection.PAGE_READWRITE; - } - - private sealed class BoundedMemoryManager : MemoryManager - { - private readonly WindowsImplementation _impl; - - public BoundedMemoryManager(WindowsImplementation impl) - { - _impl = impl; - } - - public override Memory Memory => CreateMemory(_impl._elementCount); - - protected override void Dispose(bool disposing) - { - // no-op; the handle will be disposed separately - } - - public override Span GetSpan() => _impl.Span; - - public override MemoryHandle Pin(int elementIndex) - { - if ((uint)elementIndex > (uint)_impl._elementCount) - { - throw new ArgumentOutOfRangeException(paramName: nameof(elementIndex)); - } - - bool refAdded = false; - try - { - _impl._handle.DangerousAddRef(ref refAdded); - return new MemoryHandle((T*)(_impl._handle.DangerousGetHandle() + _impl._byteOffsetIntoHandle) + elementIndex); - } - finally - { - if (refAdded) - { - _impl._handle.DangerousRelease(); - } - } - } - - public override void Unpin() - { - // no-op - we don't unpin native memory - } - } - } - - // from winnt.h - [Flags] - private enum VirtualAllocAllocationType : uint - { - MEM_COMMIT = 0x1000, - MEM_RESERVE = 0x2000, - MEM_DECOMMIT = 0x4000, - MEM_RELEASE = 0x8000, - MEM_FREE = 0x10000, - MEM_PRIVATE = 0x20000, - MEM_MAPPED = 0x40000, - MEM_RESET = 0x80000, - MEM_TOP_DOWN = 0x100000, - MEM_WRITE_WATCH = 0x200000, - MEM_PHYSICAL = 0x400000, - MEM_ROTATE = 0x800000, - MEM_LARGE_PAGES = 0x20000000, - MEM_4MB_PAGES = 0x80000000, - } - - // from winnt.h - [Flags] - private enum VirtualAllocProtection : uint - { - PAGE_NOACCESS = 0x01, - PAGE_READONLY = 0x02, - PAGE_READWRITE = 0x04, - PAGE_WRITECOPY = 0x08, - PAGE_EXECUTE = 0x10, - PAGE_EXECUTE_READ = 0x20, - PAGE_EXECUTE_READWRITE = 0x40, - PAGE_EXECUTE_WRITECOPY = 0x80, - PAGE_GUARD = 0x100, - PAGE_NOCACHE = 0x200, - PAGE_WRITECOMBINE = 0x400, - } - - [StructLayout(LayoutKind.Sequential)] - private struct MEMORY_BASIC_INFORMATION - { - public IntPtr BaseAddress; - public IntPtr AllocationBase; - public VirtualAllocProtection AllocationProtect; - public IntPtr RegionSize; - public VirtualAllocAllocationType State; - public VirtualAllocProtection Protect; - public VirtualAllocAllocationType Type; - }; - - private sealed class VirtualAllocHandle : SafeHandle - { - // Called by P/Invoke when returning SafeHandles - public VirtualAllocHandle() - : base(IntPtr.Zero, ownsHandle: true) - { - } - - // Do not provide a finalizer - SafeHandle's critical finalizer will - // call ReleaseHandle for you. - - public override bool IsInvalid => (handle == IntPtr.Zero); - - protected override bool ReleaseHandle() => - UnsafeNativeMethods.VirtualFree(handle, IntPtr.Zero, VirtualAllocAllocationType.MEM_RELEASE); - } - - private static partial class UnsafeNativeMethods - { - private const string KERNEL32_LIB = "kernel32.dll"; - - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366887(v=vs.85).aspx - [DllImport(KERNEL32_LIB, SetLastError = true)] - public static extern VirtualAllocHandle VirtualAlloc( - IntPtr lpAddress, - IntPtr dwSize, - VirtualAllocAllocationType flAllocationType, - VirtualAllocProtection flProtect); - - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366892(v=vs.85).aspx - [DllImport(KERNEL32_LIB, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool VirtualFree( - IntPtr lpAddress, - IntPtr dwSize, - VirtualAllocAllocationType dwFreeType); - - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366898(v=vs.85).aspx - [DllImport(KERNEL32_LIB, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool VirtualProtect( - IntPtr lpAddress, - IntPtr dwSize, - VirtualAllocProtection flNewProtect, - out VirtualAllocProtection lpflOldProtect); - - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366902(v=vs.85).aspx - [DllImport(KERNEL32_LIB, SetLastError = true)] - public static extern IntPtr VirtualQuery( - IntPtr lpAddress, - out MEMORY_BASIC_INFORMATION lpBuffer, - IntPtr dwLength); - } - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.cs deleted file mode 100644 index 81d359cc80e926..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/BoundedMemory.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Buffers -{ - /// - /// Represents a region of native memory. The property can be used - /// to get a backed by this memory region. - /// - public abstract class BoundedMemory : IDisposable where T : unmanaged - { - /// - /// Returns a value stating whether this native memory block is readonly. - /// - public abstract bool IsReadonly { get; } - - /// Gets the length of the instance. - public abstract int Length { get; } - - /// - /// Gets the which represents this native memory. - /// This instance must be kept alive while working with the . - /// - public abstract Memory Memory { get; } - - /// - /// Gets the which represents this native memory. - /// This instance must be kept alive while working with the . - /// - public abstract Span Span { get; } - - /// - /// Disposes this instance. - /// - public abstract void Dispose(); - - /// - /// Sets this native memory block to be readonly. Writes to this block will cause an AV. - /// This method has no effect if the memory block is zero length or if the underlying - /// OS does not support marking the memory block as readonly. - /// - public abstract void MakeReadonly(); - - /// - /// Sets this native memory block to be read+write. - /// This method has no effect if the memory block is zero length or if the underlying - /// OS does not support marking the memory block as read+write. - /// - public abstract void MakeWriteable(); - - /// - /// Gets the which represents this native memory. - /// This instance must be kept alive while working with the . - /// - public static implicit operator Span(BoundedMemory boundedMemory) => boundedMemory.Span; - - /// - /// Gets the which represents this native memory. - /// This instance must be kept alive while working with the . - /// - public static implicit operator ReadOnlySpan(BoundedMemory boundedMemory) => boundedMemory.Span; - - /// - /// Gets a reference to the element at the specified index. - /// This instance must be kept alive while working with the reference. - /// - public ref T this[int index] => ref Span[index]; - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PoisonPagePlacement.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PoisonPagePlacement.cs deleted file mode 100644 index dfbffbd99e00b3..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PoisonPagePlacement.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Buffers -{ - /// - /// Dictates where the poison page should be placed. - /// - public enum PoisonPagePlacement - { - /// - /// The poison page should be placed immediately after the memory region. - /// Attempting to access the memory page immediately following the - /// span will result in an AV. - /// - After, - - /// - /// The poison page should be placed immediately before the memory region. - /// Attempting to access the memory page immediately before the - /// span will result in an AV. - /// - Before, - } -} \ No newline at end of file diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PooledBoundedMemory.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PooledBoundedMemory.cs deleted file mode 100644 index 320f3acfe927d3..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/PooledBoundedMemory.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Buffers; -using System.Threading; - -namespace DotnetFuzzing -{ - internal sealed class PooledBoundedMemory : IDisposable where T : unmanaged - { - // Default libFuzzer max_len for inputs is 4096. - private const int MaxLength = 4096 * 2; - - private static readonly PooledBoundedMemory[] s_memoryWithPoisonBefore = new PooledBoundedMemory[MaxLength + 1]; - private static readonly PooledBoundedMemory[] s_memoryWithPoisonAfter = new PooledBoundedMemory[MaxLength + 1]; - - private readonly BoundedMemory _memory; - private readonly PooledBoundedMemory[] _pool; - - private PooledBoundedMemory(PooledBoundedMemory[] pool, int elementCount, PoisonPagePlacement placement) - { - _pool = pool; - _memory = BoundedMemory.Allocate(elementCount, placement); - } - - public BoundedMemory InnerMemory => _memory; - public Memory Memory => _memory.Memory; - public Span Span => _memory.Span; - - public void Dispose() - { - if (_pool is null || - Interlocked.CompareExchange(ref _pool[_memory.Length], this, null) != null) - { - _memory.Dispose(); - } - } - - public static PooledBoundedMemory Rent(int elementCount, PoisonPagePlacement placement) - { - if ((uint)elementCount >= MaxLength) - { - return new PooledBoundedMemory(null, elementCount, placement); - } - - PooledBoundedMemory[] pool = null; - switch (placement) - { - case PoisonPagePlacement.Before: - pool = s_memoryWithPoisonBefore; - break; - case PoisonPagePlacement.After: - pool = s_memoryWithPoisonAfter; - break; - default: - throw new ArgumentOutOfRangeException(nameof(placement)); - } - - return - Interlocked.Exchange(ref pool[elementCount], null) ?? - new PooledBoundedMemory(pool, elementCount, placement); - } - - public static PooledBoundedMemory Rent(ReadOnlySpan data, PoisonPagePlacement placement) - { - PooledBoundedMemory memory = Rent(data.Length, placement); - data.CopyTo(memory.Span); - return memory; - } - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncoding.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncoding.cs deleted file mode 100644 index 37d61c86f7a8ee..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncoding.cs +++ /dev/null @@ -1,212 +0,0 @@ -// 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.Text; -using System; - -namespace DotnetFuzzing.Fuzzers -{ - internal sealed class TextEncoding - { - static void Main(string[] args) - { - if (args.Length != 1) - { - Console.WriteLine("Usage: TextEncoding "); - return; - } - - byte[] bytes = Convert.FromBase64String(args[0]); - - TextEncoding textEncoding = new TextEncoding(); - textEncoding.FuzzTarget(bytes); - } - - public void FuzzTarget(ReadOnlySpan bytes) - { - using (PooledBoundedMemory poisonAfter = PooledBoundedMemory.Rent(bytes, PoisonPagePlacement.After)) - { - TestLatin1(poisonAfter.Span); - TestASCII(poisonAfter.Span); - TestUnicode(poisonAfter.Span); - TestUtf32(poisonAfter.Span); - TestUtf7(poisonAfter.Span); - TestUtf8(poisonAfter.Span); - } - } - - // Use individual methods for each encoding, so if there's an exception then - // it's clear which encoding failed based on the call stack. - - private static void TestLatin1(ReadOnlySpan input) - { - TestWithSubstitution(input, Encoding.GetEncoding("ISO-8859-1")); - TestWithConvert(input, Encoding.GetEncoding("ISO-8859-1")); - } - - private static void TestASCII(ReadOnlySpan input) - { - TestWithSubstitution(input, new ASCIIEncoding()); - TestWithConvert(input, new ASCIIEncoding()); - } - - private static void TestUnicode(ReadOnlySpan input) - { - TestWithSubstitution(input, new UnicodeEncoding()); - TestWithExceptions(input, new UnicodeEncoding(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: true)); - TestWithConvert(input, new UnicodeEncoding()); - } - - private static void TestUtf32(ReadOnlySpan input) - { - TestWithSubstitution(input, new UTF32Encoding()); - TestWithExceptions(input, new UTF32Encoding(bigEndian: false, byteOrderMark: false, throwOnInvalidCharacters: true)); - TestWithConvert(input, new UTF32Encoding()); - } - - private static void TestUtf7(ReadOnlySpan input) - { -#pragma warning disable SYSLIB0001 // Type or member is obsolete - TestWithSubstitution(input, new UTF7Encoding()); -#pragma warning restore SYSLIB0001 - } - - private static void TestUtf8(ReadOnlySpan input) - { - TestWithSubstitution(input, new UTF8Encoding()); - TestWithExceptions(input, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true)); - TestWithConvert(input, new UTF8Encoding()); - } - - unsafe private static void TestWithSubstitution(ReadOnlySpan input, Encoding encoding) - { - Decoder decoder = encoding.GetDecoder(); - - fixed (byte* pInput = input) - { - int charCount = encoding.GetCharCount(pInput, input.Length); - - using (PooledBoundedMemory chars = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After)) - using (PooledBoundedMemory chars2 = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After)) - using (PooledBoundedMemory bytes = PooledBoundedMemory.Rent(charCount * 4 + 2, PoisonPagePlacement.After)) - fixed (char* pchars = chars.Span) - fixed (char* pchars2 = chars2.Span) - fixed (byte* pbytes = bytes.Span) - { - if (chars.Span.Length == 0) - { - return; - } - - decoder.Reset(); - int written = decoder.GetChars(pInput, input.Length, pchars, chars.Span.Length, flush: true); - Assert.Equal(charCount, written); - - Encoder encoder = encoding.GetEncoder(); - int bytesWritten = encoder.GetBytes(pchars, chars.Span.Length, pbytes, bytes.Span.Length, flush: true); - - // Decode the encoded values. Any substitutions will be comparable now. - decoder.Reset(); - written = decoder.GetChars(pbytes, bytesWritten, pchars2, chars2.Span.Length, flush: true); - Assert.Equal(charCount, written); - - // Verify that we round-tripped the values. - Assert.SequenceEqual(chars.Span, chars2.Span); - } - } - } - - // If there are substitutions, these cases will fail with DecoderFallbackException early on, - // otherwise there should be no DecoderFallbackExceptions. - unsafe private static void TestWithExceptions(ReadOnlySpan input, Encoding encoding) - { - Assert.Equal(typeof(DecoderExceptionFallback), encoding.DecoderFallback.GetType()); - Assert.Equal(typeof(EncoderExceptionFallback), encoding.EncoderFallback.GetType()); - - Decoder decoder = encoding.GetDecoder(); - - int charCount; - try - { - fixed (byte* pinput = input) - { - charCount = decoder.GetCharCount(pinput, input.Length, flush: true); - } - } - catch (DecoderFallbackException) - { - // The input is not valid without fallbacks. - return; - } - - TestWithSubstitution(input, encoding); - } - - private static void TestWithConvert(ReadOnlySpan input, Encoding encoding) - { - // Use a few boundary cases. - TestWithConvert(input, encoding, 1); - TestWithConvert(input, encoding, 2); - TestWithConvert(input, encoding, 3); - TestWithConvert(input, encoding, 4); - TestWithConvert(input, encoding, input.Length); - - if (input.Length >= 6) - { - TestWithConvert(input, encoding, input.Length - 1); - - if (input.Length >= 12) - { - TestWithConvert(input, encoding, input.Length / 2); - } - } - } - - // Verify that obtaining data using several Convert() calls matches the result from a single GetChars() call. - unsafe private static void TestWithConvert(ReadOnlySpan input, Encoding encoding, int blockSize) - { - Decoder decoder = encoding.GetDecoder(); - Encoder encoder = encoding.GetEncoder(); - - fixed (byte* pinput = input) - { - int charCount = decoder.GetCharCount(pinput, input.Length, flush: true); - - using (PooledBoundedMemory chars = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After)) - using (PooledBoundedMemory chars2 = PooledBoundedMemory.Rent(charCount, PoisonPagePlacement.After)) - fixed (char* pchars = chars.Span) - fixed (char* pchars2 = chars2.Span) - { - decoder.Reset(); - int charsUsedTotal = 0; - int i = 0; - - while (i < input.Length) - { - bool lastIteration = i + blockSize >= input.Length; - int bytesToRead = lastIteration ? input.Length - i : blockSize; - - decoder.Convert( - bytes: pinput + i, - byteCount: bytesToRead, - chars: pchars + charsUsedTotal, - charCount: charCount - charsUsedTotal, - flush: lastIteration, - out int bytesUsed, - out int charsUsed, - out bool _); - - i += bytesUsed; - charsUsedTotal += charsUsed; - } - - Assert.Equal(charsUsedTotal, charCount); - decoder.Reset(); - decoder.GetChars(pinput, input.Length, pchars2, chars2.Span.Length, flush: true); - Assert.SequenceEqual(chars.Span, chars2.Span); - } - } - } - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.csproj b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.csproj deleted file mode 100644 index a1d11b067b3e69..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Debug - AnyCPU - {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A} - Exe - TextEncodingNetFramework - TextEncodingNetFramework - v4.8.1 - 512 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - - - ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - - - packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.sln b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.sln deleted file mode 100644 index b0c0ae2c0ee6c9..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/TextEncodingNetFramework.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.11.34929.205 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextEncodingNetFramework", "TextEncodingNetFramework.csproj", "{1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1C5896C4-4E1A-4DFF-9EA6-4B06E3132C7A}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7473A937-4769-400E-A417-BD1856CFD3B5} - EndGlobalSection -EndGlobal diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/packages.config b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/packages.config deleted file mode 100644 index bd17b9f57d3d8c..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.NetFramework/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncodingFuzzer.cs similarity index 81% rename from src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs rename to src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncodingFuzzer.cs index 8ccf6db1814e96..20982d961b51fa 100644 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncoding.cs +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncodingFuzzer.cs @@ -9,36 +9,18 @@ namespace DotnetFuzzing.Fuzzers; -internal sealed class TextEncoding : IFuzzer +// The fuzzing infrastructure currently does not support fuzzing .NET Framework. +// However, this test class, while running under .NET Core, was used to foward the fuzzing +// input to a .NET Framework console app. That app had the same test semantics as the tests +// here, although used slightly different supporting APIs since not all supporting library +// and language features are present in .NET Framework. +// This fowarding approach and .NET Framework test code is presevered in the original Pull +// Request for this file. The approach used Base64 encoding to convert the incoming +// ReadOnlySpan to a string which was then passed to the Main() method of the .NET +// Framework app which was then converted back to bytes before being passed to the .NET +// Framework fuzzing tests. +internal sealed class TextEncodingFuzzer : IFuzzer { -#if FORWARD_TO_NETFRAMEWORK - const string TestSubDirectory = @"src\libraries\Fuzzing\DotnetFuzzing\Fuzzers\TextEncoding.NetFramework\bin\Release\TextEncodingNetFramework.exe"; - //string[] IFuzzer.TargetAssemblies => ["mscorlib"]; // Not sure if this does anything. - //string[] IFuzzer.TargetCoreLibPrefixes { get; } = []; - - private static string GetRepoRootDirectory() - { - string? currentDirectory = Directory.GetCurrentDirectory(); - - while (currentDirectory != null) - { - string gitDirOrFile = Path.Combine(currentDirectory, ".git"); - if (Directory.Exists(gitDirOrFile) || File.Exists(gitDirOrFile)) - { - break; - } - currentDirectory = Directory.GetParent(currentDirectory)?.FullName; - } - - if (currentDirectory == null) - { - throw new Exception("Cannot find the git repository root"); - } - - return currentDirectory; - } -#endif - string[] IFuzzer.TargetAssemblies => []; string[] IFuzzer.TargetCoreLibPrefixes { get; } = ["System.Text"]; @@ -52,24 +34,9 @@ void IFuzzer.FuzzTarget(ReadOnlySpan bytes) TestUtf32(poisonAfter.Span); TestUtf7(poisonAfter.Span); TestUtf8(poisonAfter.Span); - -#if FORWARD_TO_NETFRAMEWORK - string path = GetRepoRootDirectory(); - path = Path.Combine(path, TestSubDirectory); - if (!File.Exists(path)) - { - Console.WriteLine($"Cannot find the .NET Framework test executable at {path}"); - } - - if (bytes.Length > 0) - { - string encoded = Convert.ToBase64String(bytes); - Process.Start(path, encoded); - } -#endif } - // Use individual methods for each encoding, so if there's an exception then + // We use individual methods for each encoding, so if there's an exception then // it's clear which encoding failed based on the call stack. private static void TestLatin1(ReadOnlySpan input) From 9ffc714f39390257890f1be721af3bda74ed1315 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 25 Jun 2024 09:55:47 -0500 Subject: [PATCH 5/6] Remove additional change --- src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj b/src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj index 740d367c40d703..fa571fbbf969ca 100644 --- a/src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj +++ b/src/libraries/Fuzzing/DotnetFuzzing/DotnetFuzzing.csproj @@ -11,10 +11,6 @@ true - - - - From 6ee823ea8b8b87a06b7aab1c42295b7f218bbcce Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Wed, 26 Jun 2024 10:17:27 -0500 Subject: [PATCH 6/6] Remove unused #define --- .../Fuzzing/DotnetFuzzing/Fuzzers/TextEncodingFuzzer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncodingFuzzer.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncodingFuzzer.cs index 20982d961b51fa..21ac6ff72a7f32 100644 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncodingFuzzer.cs +++ b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/TextEncodingFuzzer.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#define FORWARD_TO_NETFRAMEWORK - using System.Buffers; using System.Diagnostics; using System.Text;