Skip to content

[release/10.0] Fix TypeLoadException in GetMarshalAs when SafeArray has zero-length user-defined type name - #129899

Merged
steveisok merged 1 commit into
dotnet:release/10.0from
AaronRobinsonMSFT:port_124408
Jun 27, 2026
Merged

steveisok merged 1 commit into
dotnet:release/10.0from
AaronRobinsonMSFT:port_124408

Conversation

@AaronRobinsonMSFT

@AaronRobinsonMSFT AaronRobinsonMSFT commented Jun 26, 2026

Copy link
Copy Markdown
Member

Backport of #124408 to release/10.0

Customer Impact

  • Customer reported
  • Found internally

Customer reported in #124346. Getting the CustomAttributes of a ParameterInfo throws TypeLoadException when consuming tlbimp-generated COM interop assemblies (e.g. the Interop.MFilesAPI NuGet package).

A backport to .NET 10.0 was requested in #129889. This is already fixed in .NET 11.

MetadataImport.GetMarshalAs returned raw byte pointers to managed code without associated lengths. When a MarshalAs blob contains NATIVE_TYPE_SAFEARRAY with a zero-length user-defined type name (common in tlbimp-generated COM interop assemblies), the managed side used CreateReadOnlySpanFromNullTerminated to read these pointers, but the strings in the metadata blob are length-prefixed and NOT null-terminated, so this read garbage memory, producing a garbled string that failed type resolution with TypeLoadException.

The fix passes string byte counts alongside the raw pointers, converts GetMarshalAs from an FCALL to a QCall, removes overly strict asserts that forbade trailing bytes (which ECMA-335 permits and tlbimp produces), and adds a regression test.

Fixes #124346
Fixes #129889

Regression

  • Yes
  • No

Regression from commit a3dc133 (.NET 9), which switched GetMarshalAs from returning managed strings to returning raw byte pointers.

Testing

Automated regression test added (MarshalAsAttributeTests.cs) that builds a PE via PersistedAssemblyBuilder with a FieldMarshal blob reproducing the dangling pointer read.

Risk

Low. Scoped to metadata blob parsing in GetMarshalAs; the bounded-span reads and assert removals only affect the SAFEARRAY/CUSTOMMARSHALER decoding paths, and the change is covered by a new regression test.

…user-defined type name (dotnet#124408)

`MetadataImport.GetMarshalAs` returned raw byte pointers to managed code
without associated lengths. When a MarshalAs blob contains
`NATIVE_TYPE_SAFEARRAY` with a zero-length user-defined type name
(common in tlbimp-generated COM interop assemblies), the managed side
used `CreateReadOnlySpanFromNullTerminated` to read these pointers, but
the strings in the metadata blob are length-prefixed and NOT
null-terminated, so this read garbage memory, producing a garbled string
that failed type resolution with `TypeLoadException`.

Regression from commit a3dc133 which switched from returning managed
strings to returning raw byte pointers.

- Pass string byte counts (`m_cSafeArrayUserDefTypeNameBytes`,
`m_cCMMarshalerTypeNameBytes`, `m_cCMCookieStrBytes`) alongside the raw
pointers for all three strings
- Convert the `GetMarshalAs` function from an FCALL (`FCIMPL14`) to a
QCall (`extern "C" BOOL QCALLTYPE`)
- Remove overly strict `asserts
- Add regression test

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR backports the .NET 11 fix for a .NET 9/10 regression where MetadataImport.GetMarshalAs could mis-read length-prefixed metadata strings as null-terminated, producing garbage type names and throwing TypeLoadException when reflecting MarshalAs data (notably for SAFEARRAY blobs with a zero-length UDT name from tlbimp-generated COM interop assemblies).

Changes:

  • Convert GetMarshalAs from an FCALL to a QCall and include explicit UTF-8 byte lengths for returned string pointers.
  • Use bounded span decoding on the managed side (no null-terminated reads) and align SAFEARRAY UDT type resolution behavior with existing “best-effort” patterns.
  • Relax overly strict native asserts that rejected valid trailing bytes in FieldMarshal blobs, and add a regression test that reproduces the original failure mode deterministically.
Show a summary per file
File Description
src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.UnitTests/System/Runtime/InteropServices/MarshalAsAttributeTests.cs Adds a regression test PE that reproduces the zero-length SAFEARRAY UDT-name scenario and verifies no TypeLoadException is thrown.
src/coreclr/vm/qcallentrypoints.cpp Registers the new MetadataImport_GetMarshalAs QCall entrypoint.
src/coreclr/vm/mlinfo.cpp Removes exact-length debug asserts that incorrectly disallowed trailing bytes in SAFEARRAY/CUSTOMMARSHALER FieldMarshal blobs.
src/coreclr/vm/managedmdimport.hpp Moves GetMarshalAs to an extern "C" QCall and adds out-parameters for string byte lengths.
src/coreclr/vm/managedmdimport.cpp Implements the QCall version of GetMarshalAs, populating both pointers and byte counts.
src/coreclr/System.Private.CoreLib/src/System/Reflection/MdImport.cs Switches to LibraryImport QCall and decodes returned strings using explicit lengths (bounded spans), avoiding null-terminated reads.

Copilot's findings

  • Files reviewed: 7/7 changed files
  • Comments generated: 0

@AaronRobinsonMSFT

Copy link
Copy Markdown
Member Author

@steveisok @JulieLeeMSFT Failures are unrelated. This PR is green.

@steveisok
steveisok enabled auto-merge (squash) June 27, 2026 01:07
@steveisok

Copy link
Copy Markdown
Member

/ba-g Unrelated issue

@steveisok steveisok added Servicing-approved Approved for servicing release and removed Servicing-consider Issue for next servicing release review labels Jun 27, 2026
@steveisok
steveisok merged commit 62582ba into dotnet:release/10.0 Jun 27, 2026
153 of 158 checks passed
@github-project-automation github-project-automation Bot moved this to Done in AppModel Jun 27, 2026
@AaronRobinsonMSFT
AaronRobinsonMSFT deleted the port_124408 branch June 27, 2026 02:09
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-Interop-coreclr Servicing-approved Approved for servicing release

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Servicing] Backport #124408 to .NET 10 — GetMarshalAs regression on SafeArrayUserDefinedSubType breaks reflecting COM-interop attributes

5 participants