private static INativeMemory AllocateWithoutDataPopulation(int cb, PoisonPagePlacement placement) { // // PRECONDITION CHECKS // if (cb < 0) { throw new ArgumentOutOfRangeException( message: "Number of bytes to allocate must be non-negative.", paramName: nameof(cb)); } if (placement != PoisonPagePlacement.BeforeSpan && placement != PoisonPagePlacement.AfterSpan) { throw new ArgumentOutOfRangeException( message: "Invalid enum value.", paramName: nameof(placement)); } // // PROCESSING // return((Environment.OSVersion.Platform == PlatformID.Win32NT) ? AllocateWithoutDataPopulationWindows(cb, placement) /* Windows-specific code */ : AllocateWithoutDataPopulationDefault(cb) /* non-Windows-specific code */); }
/// <summary> /// Allocates a new <see cref="NativeMemory"/> region which is immediately preceded by /// or immediately followed by a poison (MEM_NOACCESS) page. If <paramref name="placement"/> /// is <see cref="PoisonPagePlacement.BeforeSpan"/>, then attempting to read the memory /// immediately before the returned <see cref="NativeMemory"/> will result in an AV. /// If <paramref name="placement"/> is <see cref="PoisonPagePlacement.AfterSpan"/>, then /// attempting to read the memory immediately after the returned <see cref="NativeMemory"/> /// will result in AV. /// </summary> /// <remarks> /// The newly-allocated memory will be populated with random data. /// </remarks> public static INativeMemory Allocate(int cb, PoisonPagePlacement placement) { var retVal = AllocateWithoutDataPopulation(cb, placement); new Random().NextBytes(retVal.Span); // doesn't need to be cryptographically strong return(retVal); }
private static INativeMemory AllocateWithoutDataPopulationWindows(int cb, PoisonPagePlacement placement) { long totalBytesToAllocate = cb; checked { // We only need to round cb up if it's not an exact multiple // of the system page size. var leftoverBytes = cb % 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. var 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) { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); 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 VirtualAllocWrapper( handle: handle, offset: (placement == PoisonPagePlacement.BeforeSpan) ? SystemPageSize /* just after leading poison page */ : checked ((int)(totalBytesToAllocate - SystemPageSize - cb)) /* just before trailing poison page */, length: cb) { Protection = VirtualAllocProtection.PAGE_READWRITE }); }
/// <summary> /// Allocates a new <see cref="BoundedMemory{T}"/> region which is immediately preceded by /// or immediately followed by a poison (MEM_NOACCESS) page. If <paramref name="placement"/> /// is <see cref="PoisonPagePlacement.Before"/>, then attempting to read the memory /// immediately before the returned <see cref="BoundedMemory{T}"/> will result in an AV. /// If <paramref name="placement"/> is <see cref="PoisonPagePlacement.After"/>, then /// attempting to read the memory immediately after the returned <see cref="BoundedMemory{T}"/> /// will result in AV. /// </summary> /// <remarks> /// The newly-allocated memory will be populated with random data. /// </remarks> public static BoundedMemory <T> Allocate <T>(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)); } var retVal = AllocateWithoutDataPopulation <T>(elementCount, placement); FillRandom(MemoryMarshal.AsBytes(retVal.Span)); return(retVal); }
private static UnixImplementation <T> AllocateWithoutDataPopulation <T>(int elementCount, PoisonPagePlacement placement) where T : unmanaged { // On non-Windows platforms, we don't yet have support for changing the permissions of individual pages. return(new UnixImplementation <T>(elementCount)); }
public BoundedUtf8Span(ReadOnlySpan <byte> utf8Data, PoisonPagePlacement placement = PoisonPagePlacement.After) { _boundedMemory = BoundedMemory.AllocateFromExistingData(utf8Data, placement); }
public BoundedUtf8Span(ReadOnlySpan <char> utf16Data, PoisonPagePlacement placement = PoisonPagePlacement.After) : this(u8(utf16Data.ToString()).AsBytes(), placement) { }
/// <summary> /// Similar to <see cref="Allocate(int, PoisonPagePlacement)"/>, but populates the allocated /// native memory block from existing data rather than using random data. /// </summary> public static BoundedMemory <T> AllocateFromExistingData <T>(T[] data, PoisonPagePlacement placement = PoisonPagePlacement.After) where T : unmanaged { return(AllocateFromExistingData(new ReadOnlySpan <T>(data), placement)); }
/// <summary> /// Similar to <see cref="Allocate(int, PoisonPagePlacement)"/>, but populates the allocated /// native memory block from existing data rather than using random data. /// </summary> public static BoundedMemory <T> AllocateFromExistingData <T>(ReadOnlySpan <T> data, PoisonPagePlacement placement = PoisonPagePlacement.After) where T : unmanaged { if (placement != PoisonPagePlacement.Before && placement != PoisonPagePlacement.After) { throw new ArgumentOutOfRangeException(nameof(placement)); } var retVal = AllocateWithoutDataPopulation <T>(data.Length, placement); data.CopyTo(retVal.Span); return(retVal); }
private static UnixImplementation <T> AllocateWithoutDataPopulationUnix <T>(int elementCount, PoisonPagePlacement placement) where T : unmanaged { // On non-Windows platforms, we don't yet have support for changing the permissions of individual pages. // We'll instead use AllocHGlobal / FreeHGlobal to carve out a r+w section of unmanaged memory. return(new UnixImplementation <T>(elementCount)); }
/// <summary> /// Similar to <see cref="Allocate(int, PoisonPagePlacement)"/>, but populates the allocated /// native memory block from existing data rather than using random data. /// </summary> public static INativeMemory AllocateFromExistingData(ReadOnlySpan <byte> data, PoisonPagePlacement placement) { var retVal = AllocateWithoutDataPopulation(data.Length, placement); data.CopyTo(retVal.Span); return(retVal); }
private static BoundedMemory <T> AllocateWithoutDataPopulation <T>(int elementCount, PoisonPagePlacement placement) where T : unmanaged { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return(AllocateWithoutDataPopulationWindows <T>(elementCount, placement)); } else { return(AllocateWithoutDataPopulationUnix <T>(elementCount, placement)); } }
public BoundedUtf8Span(string utf16Data, PoisonPagePlacement placement = PoisonPagePlacement.After) : this(utf16Data.AsSpan(), placement) { }