From c936bf27363ab2b7288774ee296a27fb003a0727 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 18 Mar 2026 10:24:37 +0100 Subject: [PATCH 1/2] [tests] Fix BitmapContextTest.CreateAdaptive_3/_4 crash due to undersized buffer CreateAdaptive_3 and CreateAdaptive_4 used a hardcoded 512-byte buffer for a 256x256 adaptive bitmap context. When ToImage() called CGBitmapContextCreateImage, CoreGraphics tried to copy the bitmap data from this tiny buffer, reading past the allocation and causing a SIGSEGV. Fix CreateAdaptive_3 by computing the buffer size from parameters.AlignedBytesPerRow * parameters.Height (matching CreateAdaptive_2). Fix CreateAdaptive_4 by using a 1MB buffer, large enough for any 256x256 adaptive bitmap format. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/monotouch-test/CoreGraphics/BitmapContextTest.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/monotouch-test/CoreGraphics/BitmapContextTest.cs b/tests/monotouch-test/CoreGraphics/BitmapContextTest.cs index 14e54125e5b6..921f011dce41 100644 --- a/tests/monotouch-test/CoreGraphics/BitmapContextTest.cs +++ b/tests/monotouch-test/CoreGraphics/BitmapContextTest.cs @@ -205,7 +205,6 @@ public void CreateAdaptive_3 () var calledOnLockPointer = false; var calledOnUnlockPointer = false; var calledOnReleaseInfo = false; - const int renderingBufferProviderSize = 512; var calledOnResolve = false; var calledOnAllocate = false; @@ -225,10 +224,11 @@ public void CreateAdaptive_3 () (ref CGContentInfo info, ref CGBitmapParameters parameters) => { // TestRuntime.NSLog ($"CreateAdaptive () OnAllocate#3 info={info} parameters={parameters}"); calledOnAllocate = true; + var renderingBufferProviderSize = checked(parameters.AlignedBytesPerRow * parameters.Height); var renderingBufferProvider = CGRenderingBufferProvider.Create (IntPtr.Zero, renderingBufferProviderSize, lockPointer: (info) => { calledOnLockPointer = true; - var rv = Marshal.AllocHGlobal (renderingBufferProviderSize); + var rv = Marshal.AllocHGlobal (checked((nint) renderingBufferProviderSize)); // TestRuntime.NSLog ($"CreateAdaptive3 () OnLockPointer#3 (0x{info:x}) => 0x{rv:x}"); return rv; }, @@ -280,7 +280,9 @@ public void CreateAdaptive_4 () var calledOnLockPointer = false; var calledOnUnlockPointer = false; var calledOnReleaseInfo = false; - const int renderingBufferProviderSize = 512; + // Must be large enough to hold the bitmap data for a 256x256 adaptive context. + // Float16 RGBA = 8 bytes/pixel, so 256 * 8 = 2048 bytes/row, * 256 rows = 524,288 bytes minimum. + const int renderingBufferProviderSize = 1024 * 1024; using (var pool = new NSAutoreleasePool ()) { using (var renderingBufferProvider = CGRenderingBufferProvider.Create (IntPtr.Zero, renderingBufferProviderSize, From 0511a9d5b7278be792c02849cf13bc745410d8dd Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 18 Mar 2026 11:24:09 +0100 Subject: [PATCH 2/2] CreateAdaptive_4: compute buffer size from parameters instead of hardcoding Address review feedback: replace the hardcoded 1MB buffer with a properly computed size from parameters.AlignedBytesPerRow * parameters.Height in OnAllocate, matching the pattern used in CreateAdaptive_2 and _3. The buffer provider is now created inside OnAllocate and stored in an outer-scope variable so it can be explicitly disposed afterward to verify the releaseInfo callback is called. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../CoreGraphics/BitmapContextTest.cs | 115 +++++++++--------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/tests/monotouch-test/CoreGraphics/BitmapContextTest.cs b/tests/monotouch-test/CoreGraphics/BitmapContextTest.cs index 921f011dce41..fc32c3fbac7c 100644 --- a/tests/monotouch-test/CoreGraphics/BitmapContextTest.cs +++ b/tests/monotouch-test/CoreGraphics/BitmapContextTest.cs @@ -280,71 +280,70 @@ public void CreateAdaptive_4 () var calledOnLockPointer = false; var calledOnUnlockPointer = false; var calledOnReleaseInfo = false; - // Must be large enough to hold the bitmap data for a 256x256 adaptive context. - // Float16 RGBA = 8 bytes/pixel, so 256 * 8 = 2048 bytes/row, * 256 rows = 524,288 bytes minimum. - const int renderingBufferProviderSize = 1024 * 1024; + CGRenderingBufferProvider? externalBufferProvider = null; using (var pool = new NSAutoreleasePool ()) { - using (var renderingBufferProvider = CGRenderingBufferProvider.Create (IntPtr.Zero, renderingBufferProviderSize, - lockPointer: (info) => { - calledOnLockPointer = true; - var rv = Marshal.AllocHGlobal (renderingBufferProviderSize); - // TestRuntime.NSLog ($"CreateAdaptive () OnLockPointer#4 (0x{info:x}) => 0x{rv:x}"); - return rv; + var calledOnResolve = false; + var calledOnAllocate = false; + var calledOnFree = false; + var calledOnError = false; + var options = new CGAdaptiveOptions () { + MaximumBitDepth = CGComponent.Float16Bit, + }; + + using (var context = CGBitmapContext.Create (width, height, options, + (ref CGContentInfo info, ref CGBitmapParameters parameters) => { + // TestRuntime.NSLog ($"CreateAdaptive () OnResolve#4 info={info} parameters={parameters}"); + calledOnResolve = true; + return true; }, - unlockPointer: (info, pointer) => { - // TestRuntime.NSLog ($"CreateAdaptive () OnUnlockPointer#4 (0x{info:x}, 0x{pointer:x})"); - calledOnUnlockPointer = true; - Marshal.FreeHGlobal (pointer); + (ref CGContentInfo info, ref CGBitmapParameters parameters) => { + // TestRuntime.NSLog ($"CreateAdaptive () OnAllocate#4 info={info} parameters={parameters}"); + calledOnAllocate = true; + var renderingBufferProviderSize = checked(parameters.AlignedBytesPerRow * parameters.Height); + externalBufferProvider = CGRenderingBufferProvider.Create (IntPtr.Zero, renderingBufferProviderSize, + lockPointer: (info) => { + calledOnLockPointer = true; + var rv = Marshal.AllocHGlobal (checked((nint) renderingBufferProviderSize)); + // TestRuntime.NSLog ($"CreateAdaptive () OnLockPointer#4 (0x{info:x}) => 0x{rv:x}"); + return rv; + }, + unlockPointer: (info, pointer) => { + // TestRuntime.NSLog ($"CreateAdaptive () OnUnlockPointer#4 (0x{info:x}, 0x{pointer:x})"); + calledOnUnlockPointer = true; + Marshal.FreeHGlobal (pointer); + }, + releaseInfo: (info) => { + // TestRuntime.NSLog ($"CreateAdaptive () OnReleaseInfo#4 (0x{info:x})"); + calledOnReleaseInfo = true; + } + ); + return externalBufferProvider; + }, + (CGRenderingBufferProvider renderingBufferProvider, ref CGContentInfo contentInfo, ref CGBitmapParameters bitmapParameters) => { + // TestRuntime.NSLog ($"CreateAdaptive () OnFree#4 renderingBufferProvider={renderingBufferProvider} contentInfo={contentInfo} bitmapParameters={bitmapParameters}"); + calledOnFree = true; }, - releaseInfo: (info) => { - // TestRuntime.NSLog ($"CreateAdaptive () OnReleaseInfo#4 (0x{info:x})"); - calledOnReleaseInfo = true; - } - )) { - Assert.That (renderingBufferProvider, Is.Not.Null, "RenderingBufferProvider"); - - var calledOnResolve = false; - var calledOnAllocate = false; - var calledOnFree = false; - var calledOnError = false; - var options = new CGAdaptiveOptions () { - MaximumBitDepth = CGComponent.Float16Bit, - }; - - using (var context = CGBitmapContext.Create (width, height, options, - (ref CGContentInfo info, ref CGBitmapParameters parameters) => { - // TestRuntime.NSLog ($"CreateAdaptive () OnResolve#4 info={info} parameters={parameters}"); - calledOnResolve = true; - return true; - }, - (ref CGContentInfo info, ref CGBitmapParameters parameters) => { - // TestRuntime.NSLog ($"CreateAdaptive () OnAllocate#4 info={info} parameters={parameters}"); - calledOnAllocate = true; - return renderingBufferProvider; - }, - (CGRenderingBufferProvider renderingBufferProvider, ref CGContentInfo contentInfo, ref CGBitmapParameters bitmapParameters) => { - // TestRuntime.NSLog ($"CreateAdaptive () OnFree#4 renderingBufferProvider={renderingBufferProvider} contentInfo={contentInfo} bitmapParameters={bitmapParameters}"); - calledOnFree = true; - }, - (NSError error, ref CGContentInfo contentInfo, ref CGBitmapParameters bitmapParameters) => { - // TestRuntime.NSLog ($"CreateAdaptive () OnError#4 error={error} contentInfo={contentInfo} bitmapParameters={bitmapParameters}"); - calledOnError = true; - })) { - - Assert.NotNull (context, "Context#4"); - - using var img = context.ToImage (); - Assert.NotNull (img, "ToImage"); - } - - Assert.That (calledOnResolve, Is.True, "calledOnResolve#4"); - Assert.That (calledOnAllocate, Is.True, "calledOnAllocate#4"); - Assert.That (calledOnFree, Is.True, "calledOnFree#4"); - Assert.That (calledOnError, Is.False, "calledOnError#4"); + (NSError error, ref CGContentInfo contentInfo, ref CGBitmapParameters bitmapParameters) => { + // TestRuntime.NSLog ($"CreateAdaptive () OnError#4 error={error} contentInfo={contentInfo} bitmapParameters={bitmapParameters}"); + calledOnError = true; + })) { + + Assert.NotNull (context, "Context#4"); + + using var img = context.ToImage (); + Assert.NotNull (img, "ToImage"); } + + Assert.That (calledOnResolve, Is.True, "calledOnResolve#4"); + Assert.That (calledOnAllocate, Is.True, "calledOnAllocate#4"); + Assert.That (calledOnFree, Is.True, "calledOnFree#4"); + Assert.That (calledOnError, Is.False, "calledOnError#4"); } + // Explicitly dispose the buffer provider to verify releaseInfo is called. + externalBufferProvider?.Dispose (); + Assert.That (calledOnLockPointer, Is.True, "calledOnLockPointer#4"); Assert.That (calledOnUnlockPointer, Is.True, "calledOnUnlockPointer#4"); Assert.That (calledOnReleaseInfo, Is.True, "calledOnReleaseInfo#4");