Skip to content

C++/CLI marshaling of wchar_t* buffer causes silent CLR heap corruption #130225

Description

@Applifox

Description

BUG REPORT: C++/CLI marshaling of wchar_t* buffer causes silent CLR heap corruption

Summary

Calling a native DLL function that writes into a wchar_t* buffer using an incorrect
C++/CLI marshaling pattern does not produce any compiler error or runtime error.
Instead, the CLR heap becomes corrupted, and the crash appears later in unrelated
WinForms code (new Form() or Form.Show()), producing misleading exceptions.

The incorrect code compiles and runs, but silently corrupts memory.

Environment

  • Windows 11 x64
  • Visual Studio 2022
  • .NET Framework 4.8 (C++/CLI)
  • Native C++ DLL exporting:
    int __stdcall Camera_EnumDevices(wchar_t* buffer, int bufferSize);

Incorrect C++/CLI code (compiles, runs, but corrupts memory)

StringBuilder^ sb = gcnew StringBuilder(2048);
pin_ptr<const wchar_t> pBuffer = PtrToStringChars(sb->ToString());
int count = Camera_EnumDevices((wchar_t*)pBuffer, sb->Capacity);

Problem

sb->ToString() creates a new immutable System.String, not the internal buffer of
the StringBuilder. The native function writes into this immutable string, causing
heap corruption.

The corruption does not crash immediately. Instead, it crashes later inside
WinForms code, for example:

var f = new Form2();
f.Show();   // crash here

The exception is misleading and unrelated to the actual cause.

Correct C++/CLI code (works, no corruption)

array<wchar_t>^ buf = gcnew array<wchar_t>(2048);
pin_ptr<wchar_t> pBuffer = &buf[0];

int count = Camera_EnumDevices(pBuffer, 2048);

String^ names = gcnew String(pBuffer);

Expected behavior

  • Compiler warning or error when attempting to pin an immutable string for native
    write access.
  • Runtime protection or exception when native code writes into immutable managed
    memory.

Actual behavior

  • Code compiles without warnings.
  • Code runs without immediate error.
  • Native write corrupts CLR heap.
  • Crash occurs later in unrelated WinForms code.
  • Exception message does not indicate the real cause.

Impact

This issue is extremely hard to diagnose because:

  • The crash happens far away from the actual bug.
  • The exception message is misleading.
  • The incorrect code looks valid and compiles cleanly.
  • Developers may incorrectly blame WinForms, COM, Media Foundation, or MFStartup/MFShutdown.

Reproduction Steps

Reproduce

The following C++/CLI code compiles and executes without any warnings or errors,
but causes silent CLR heap corruption. The crash appears later in unrelated
WinForms code (new Form() or Form.Show()), producing misleading exceptions.

Incorrect version (causes CLR heap corruption)

List<String^>^ CameraWrapper::GetCameraList()
{
    StringBuilder^ sb = gcnew StringBuilder(2048);

    // Pinning an immutable System.String created by ToString()
    pin_ptr<const wchar_t> pBuffer = PtrToStringChars(sb->ToString());
    int count = Camera_EnumDevices((wchar_t*)pBuffer, sb->Capacity);

    if (count <= 0)
        return gcnew List<String^>();

    String^ names = gcnew String(pBuffer);
    array<String^>^ arr = names->Split(
        gcnew array<wchar_t>{'|'},
        StringSplitOptions::RemoveEmptyEntries);

    return gcnew List<String^>(arr);
}

This code silently corrupts the CLR heap because sb->ToString() returns a new
immutable System.String, not the internal buffer of the StringBuilder. The native
function writes into this immutable string, corrupting memory.

The corruption does not crash immediately. Instead, it crashes later inside
WinForms code, for example:

var f = new Form2();
f.Show();   // crash here

Correct version (no heap corruption)

List<String^>^ CameraWrapper::GetCameraList()
{
    const int maxChars = 2048;

    // Managed mutable buffer
    array<wchar_t>^ buf = gcnew array<wchar_t>(maxChars);

    // Pin the actual writable buffer
    pin_ptr<wchar_t> pBuffer = &buf[0];

    int count = Camera_EnumDevices(pBuffer, maxChars);

    if (count <= 0)
        return gcnew List<String^>();

    String^ names = gcnew String(pBuffer);
    array<String^>^ arr = names->Split(
        gcnew array<wchar_t>{'|'},
        StringSplitOptions::RemoveEmptyEntries);

    return gcnew List<String^>(arr);
}

This version uses a real mutable buffer, which the native code can safely write
into. No heap corruption occurs and WinForms no longer crashes.

Expected behavior

List<String^>^ CameraWrapper::GetCameraList()
{
    const int maxChars = 2048;

    // Managed mutable buffer
    array<wchar_t>^ buf = gcnew array<wchar_t>(maxChars);

    // Pin the actual writable buffer
    pin_ptr<wchar_t> pBuffer = &buf[0];

    int count = Camera_EnumDevices(pBuffer, maxChars);

    if (count <= 0)
        return gcnew List<String^>();

    String^ names = gcnew String(pBuffer);
    array<String^>^ arr = names->Split(
        gcnew array<wchar_t>{'|'},
        StringSplitOptions::RemoveEmptyEntries);

    return gcnew List<String^>(arr);
}

Actual behavior

List<String^>^ CameraWrapper::GetCameraList()
{
    StringBuilder^ sb = gcnew StringBuilder(2048);

    // Pinning an immutable System.String created by ToString()
    pin_ptr<const wchar_t> pBuffer = PtrToStringChars(sb->ToString());
    int count = Camera_EnumDevices((wchar_t*)pBuffer, sb->Capacity);

    if (count <= 0)
        return gcnew List<String^>();

    String^ names = gcnew String(pBuffer);
    array<String^>^ arr = names->Split(
        gcnew array<wchar_t>{'|'},
        StringSplitOptions::RemoveEmptyEntries);

    return gcnew List<String^>(arr);
}

does not generate any exception during it's execution, but impact CLR heap

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

Closing note: Diagnostics on 5.7.2026 from 15:00 to 18:30 confirmed silent CLR heap corruption caused by incorrect C++/CLI marshaling.

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
No status

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions