Skip to content

Double-free in getUniformBlockIndex causes heap corruption / random crashes #55

Description

@macbearchen

Double-free in getUniformBlockIndex causes heap corruption / random crashes

Description

In flutter_angle/lib/src/desktop/wrapper.dart, RenderingContext.getUniformBlockIndex() allocates native memory via toNativeUtf8(), then creates a second Pointer that reinterprets the same memory address, and subsequently frees both pointers — resulting in a double-free.

int getUniformBlockIndex(Program program, String uniformBlockName){
  var sourceString = uniformBlockName.toNativeUtf8();
  var arrayPointer = Pointer<Char>.fromAddress(sourceString.address);
  int i = gl.glGetUniformBlockIndex(program.id, arrayPointer);
  calloc.free(arrayPointer);   // <-- frees memory at `sourceString.address`
  calloc.free(sourceString);   // <-- frees the SAME memory again
  return i;
}

Pointer<Char>.fromAddress(sourceString.address) does not allocate new memory — it only creates a differently-typed view over the address that sourceString already points to. Calling calloc.free() on both pointers frees the same block of memory twice.

Impact

Double-frees corrupt the heap's internal allocator metadata but don't necessarily crash immediately. In our case, the corruption went undetected until an unrelated free() call elsewhere in the app (triggered by an Impeller/Skia image-encode callback, e.g. toImage()) hit macOS's malloc guard, producing:

Application Specific Information:
abort() called
malloc_vreport
malloc_report
___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED

This makes the bug very difficult to trace back to its source, since the crash surfaces far away (in unrelated code) from the actual double-free call site, and it may appear intermittently depending on heap layout and allocation timing.

Steps to Reproduce

  1. Call RenderingContext.getUniformBlockIndex(program, blockName) for any UBO-based shader setup (e.g. lighting data via layout(std140) uniform ...).
  2. Continue running the app; unrelated memory operations elsewhere (e.g. taking a screenshot, calling toImage(), or simply enough allocator churn over time) may eventually trigger abort() via the OS malloc guard.
  3. Crash is intermittent and not tied to any obvious action, since it depends on heap allocator state at the time of the next unrelated free.

Expected Behavior

Only the originally-allocated pointer (sourceString) should be freed once, since arrayPointer is not a separate allocation.

Suggested Fix

int getUniformBlockIndex(Program program, String uniformBlockName){
  var sourceString = uniformBlockName.toNativeUtf8();
  var arrayPointer = Pointer<Char>.fromAddress(sourceString.address);
  int i = gl.glGetUniformBlockIndex(program.id, arrayPointer);
  calloc.free(sourceString); // free only once — arrayPointer is a view, not a separate allocation
  return i;
}

Environment

  • flutter_angle version: 0.4.1
  • Platform: macOS (Apple Silicon, arm64)
  • OS: macOS 26.5.2 (25F84)
  • Hardware: Mac mini (M1)

Crash Log Excerpt

Exception Type:    EXC_CRASH (SIGABRT)
Termination Reason: Namespace SIGNAL, Code 6, Abort trap: 6

Thread 0 Crashed:
0   libsystem_kernel.dylib    __pthread_kill
1   libsystem_pthread.dylib   pthread_kill
2   libsystem_c.dylib         abort
3   libsystem_malloc.dylib    malloc_vreport
4   libsystem_malloc.dylib    malloc_report
5   libsystem_malloc.dylib    ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED
...
62  FlutterMacOS  dart::DartEntry::InvokeFunction(...)
...
66  FlutterMacOS  flutter::(anonymous namespace)::EncodeImageAndInvokeDataCallback(...)

(Note: the crash surfaces during an unrelated image-encode callback, not at the getUniformBlockIndex call site itself — consistent with delayed heap-corruption detection.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions