[release/10.0] Fix TypeLoadException in GetMarshalAs when SafeArray has zero-length user-defined type name - #129899
Merged
Conversation
…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>
Contributor
There was a problem hiding this comment.
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
GetMarshalAsfrom 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
jkotas
approved these changes
Jun 26, 2026
Member
Author
|
@steveisok @JulieLeeMSFT Failures are unrelated. This PR is green. |
steveisok
enabled auto-merge (squash)
June 27, 2026 01:07
Member
|
/ba-g Unrelated issue |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backport of #124408 to release/10.0
Customer Impact
Customer reported in #124346. Getting the
CustomAttributesof aParameterInfothrowsTypeLoadExceptionwhen consuming tlbimp-generated COM interop assemblies (e.g. theInterop.MFilesAPINuGet package).A backport to .NET 10.0 was requested in #129889. This is already fixed in .NET 11.
MetadataImport.GetMarshalAsreturned raw byte pointers to managed code without associated lengths. When aMarshalAsblob containsNATIVE_TYPE_SAFEARRAYwith a zero-length user-defined type name (common in tlbimp-generated COM interop assemblies), the managed side usedCreateReadOnlySpanFromNullTerminatedto 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 withTypeLoadException.The fix passes string byte counts alongside the raw pointers, converts
GetMarshalAsfrom 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
Regression from commit a3dc133 (.NET 9), which switched
GetMarshalAsfrom returning managed strings to returning raw byte pointers.Testing
Automated regression test added (
MarshalAsAttributeTests.cs) that builds a PE viaPersistedAssemblyBuilderwith 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.