Skip to content

Fix IJW OverflowException with 17+ by-ref parameters#127182

Open
jkoritzinsky wants to merge 5 commits intodotnet:mainfrom
jkoritzinsky:ijw-sig-fix
Open

Fix IJW OverflowException with 17+ by-ref parameters#127182
jkoritzinsky wants to merge 5 commits intodotnet:mainfrom
jkoritzinsky:ijw-sig-fix

Conversation

@jkoritzinsky
Copy link
Copy Markdown
Member

Note

This PR was authored with the assistance of GitHub Copilot.

Summary

Fixes #127166 — IJW (C++/CLI) reverse P/Invoke throws OverflowException when calling a managed function with 17+ by-ref parameters from native code.

Root Cause

StubSigBuilder::EnsureEnoughQuickBytes only doubled the buffer size once. When FunctionSigBuilder::SetSig needed to store a 524-byte parameter signature (18 by-ref params with custom modifiers), the buffer grew from 256 → 512 bytes — still in the CQuickBytes inline buffer. Writing 524 bytes overflowed the 512-byte inline buffer by 12 bytes into the adjacent m_nItems field, corrupting the arg count from 18 to garbage (~0x2800220F). This caused GetSigSize() to compute an overflowed S_UINT32 and throw COR_E_OVERFLOW.

Why This Regressed

PR #106000 changed ConvertToInternalSignature to preserve custom modifiers (bSkipCustomModifier=FALSE). Each preserved modifier adds 10 bytes (ELEMENT_TYPE_CMOD_INTERNAL + required byte + 8-byte TypeHandle pointer). For 18+ parameters with NeedsCopyConstructorModifier, the signature exceeded 512 bytes for the first time, triggering the latent buffer overflow in EnsureEnoughQuickBytes.

Fix

Loop the doubling in EnsureEnoughQuickBytes until the buffer is large enough to hold the requested data.

Regression Test

Added an IJW test with a managed function that has 18 by-ref parameters (const int& and const IntWrapper&), called from native #pragma unmanaged code. The test verifies the correct sum (153) is returned.

StubSigBuilder::EnsureEnoughQuickBytes only doubled the buffer size once,
so when the required size exceeded 2x the current buffer, the CQuickBytes
inline buffer (512 bytes) overflowed into the adjacent m_nItems field.

This was triggered by PR dotnet#106000 which changed ConvertToInternalSignature
to preserve custom modifiers (bSkipCustomModifier=FALSE). Each preserved
modifier adds 10 bytes (ELEMENT_TYPE_CMOD_INTERNAL + required byte +
8-byte TypeHandle pointer) to the signature. For 18+ parameters with
custom modifiers, the total signature exceeded 512 bytes, causing a
buffer overflow that corrupted m_nItems and led to an OverflowException.

The fix loops the doubling until the buffer is large enough.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an IJW (C++/CLI) reverse P/Invoke signature-building buffer growth bug that can corrupt signature state and surface as an OverflowException when native code calls managed methods with many by-ref parameters (and preserved custom modifiers).

Changes:

  • Update StubSigBuilder::EnsureEnoughQuickBytes to keep doubling until the requested append size fits.
  • Add an IJW regression test that exercises a native→managed call with 18 by-ref parameters and validates the returned sum.
  • Extend the IJW native/managed mixed-mode test DLL with helper entrypoints to drive the scenario.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/coreclr/vm/stubgen.cpp Fixes the QuickBytes growth logic to prevent signature buffer overwrite/corruption.
src/tests/Interop/IJW/NativeCallingManaged/NativeCallingManaged.cs Adds a managed-side regression test case invoking the new IJW scenario via reflection.
src/tests/Interop/IJW/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp Adds a mixed-mode native→managed call chain with 18 by-ref parameters to reproduce the prior failure.

Comment thread src/coreclr/vm/stubgen.cpp Outdated
Comment thread src/tests/Interop/IJW/IjwNativeCallingManagedDll/IjwNativeCallingManagedDll.cpp Outdated
- Use S_SIZE_T for checked arithmetic in EnsureEnoughQuickBytes to
  prevent infinite loop or undersized allocation on SIZE_T overflow.
- Remove unnecessary NativeSum18ByRefEntryPoint DLL export.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/coreclr/vm/stubgen.cpp Outdated
Comment thread src/coreclr/vm/stubgen.cpp Outdated
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Copilot AI review requested due to automatic review settings April 21, 2026 00:05
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Comment thread src/coreclr/vm/stubgen.cpp Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +2008 to +2011
SIZE_T cbNew = max(m_cbSig + cbToAppend, 2 * cbBuffer);
// Detect integer overflow
if ((cbNew - m_cbSig) < cbToAppend)
COMPlusThrowHR(COR_E_OVERFLOW);
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overflow check here can be defeated because the potentially overflowing operations happen before the check (m_cbSig + cbToAppend and 2 * cbBuffer can wrap in SIZE_T). Consider doing the size calculations with S_SIZE_T/ClrSafeInt (including the doubling) and checking IsOverflow() before resizing, instead of relying on post-hoc subtraction with unsigned values.

Suggested change
SIZE_T cbNew = max(m_cbSig + cbToAppend, 2 * cbBuffer);
// Detect integer overflow
if ((cbNew - m_cbSig) < cbToAppend)
COMPlusThrowHR(COR_E_OVERFLOW);
S_SIZE_T cbRequired = S_SIZE_T(m_cbSig) + S_SIZE_T(cbToAppend);
if (cbRequired.IsOverflow())
COMPlusThrowHR(COR_E_OVERFLOW);
S_SIZE_T cbDoubled = S_SIZE_T(cbBuffer) * S_SIZE_T(2);
SIZE_T cbNew = cbRequired.Value();
if (!cbDoubled.IsOverflow())
{
cbNew = max(cbNew, cbDoubled.Value());
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[C++/CLI] [IJW] HRException crash when calling a managed function with 17+ parameters from native code (regression from .NET 8)

4 participants