Validate invalid base type declarations in ILVerify#129118
Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/jit-contrib |
|
@dotnet-policy-service agree |
| TypeSpecification typeSpecification = _module.MetadataReader.GetTypeSpecification(typeSpecificationHandle); | ||
| BlobReader signatureReader = _module.MetadataReader.GetBlobReader(typeSpecification.Signature); | ||
|
|
||
| if (signatureReader.ReadSignatureTypeCode() != SignatureTypeCode.GenericTypeInstance) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| int genericTypeKind = signatureReader.ReadCompressedInteger(); | ||
| if (genericTypeKind != (int)SignatureTypeKind.Class) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| EntityHandle genericTypeHandle = signatureReader.ReadTypeHandle(); | ||
| if (genericTypeHandle.Kind != HandleKind.TypeDefinition && | ||
| genericTypeHandle.Kind != HandleKind.TypeReference) | ||
| { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Is this any better than just calling _module.GetType(typeHandle)?
There was a problem hiding this comment.
I tried that locally again, using only _module.GetType(typeHandle) does not catch the #119536 repro. (ObjectTypeSpecBase_InvalidType_InvalidBaseType)
In the extends object case, _module.GetType(typeHandle) resolves the TypeSpec to System.Object, which loses the issue that the base type was encoded as an invalid TypeSpec in the extends clause.
There was a problem hiding this comment.
If you would like to check for invalid type specs, it should be done in the central location where typespecs are decoded so that it applies to all of them. The problem is not specific to base type.
There was a problem hiding this comment.
Thanks, I tried moving this into EcmaModule.GetType, but I don't think this check can be global.
EcmaModule.GetType is context-independent. A top-level built-in TypeSpec like ELEMENT_TYPE_OBJECT is invalid in a TypeDef extends clause, but it is not invalid in every context. For example, CoreCLR accepts stobj with a TypeSpec object token, and ILVerification already has a similar existing test case using stobj string ( [LoadStoreIndirectTests] StoreObject.ValidTypeToken).
Rejecting these TypeSpecs in EcmaModule.GetType would therefore make ILVerify reject IL that is accepted outside of base type declarations. I think this validation should stay in the base type verification path, where we know the token is being used as a class base type.
There was a problem hiding this comment.
TypeSpec with object token is invalid in every context according to the spec. Look for TypeSpecBlob grammar in ECMA-335.
ILVerify is meant to check for speced behavior. It is not meant to check for what CoreCLR happens to accept. The runtime often accepts more than what's allowed by the spec.
There was a problem hiding this comment.
Thanks for clarifying. I understand now.
I updated the change so malformed TypeSpecs are validated in EcmaModule.GetType, and added related test coverage.
After making that change, I found a few existing ILVerification tests that were also using invalid TypeSpecs, so I updated them as well:
- In
LoadStoreIndirectTests.il, changedstobj stringtostobj [System.Runtime]System.String. - In
CastingTests.il, replacedBoxByRefInt_Invalid_BoxByRefwithBoxIntPointer_Invalid_UnmanagedPointer, so the test no longer depends on an invalid TypeSpec. - For
.override method instance string class A.T1::Func1()-style cases, removed the unnecessaryclassmodifier. Whenclassis present, ILAsm emits aTypeSpecas theMemberRef.Parentfor a non-generic class, instead of using aTypeDeforTypeRef. This is invalid according to theTypeSpecBlobgrammar.
Please let me know if you would prefer any of these test updates to be split out or adjusted differently.
a03f2df to
87dc394
Compare
|
Hi @jkotas, just checking in on this PR. I’ve addressed the feedback and made the requested updates. Please let me know if there’s anything else I should adjust. Thanks! |
| #if ILVERIFICATION | ||
| private static void ValidateTypeSpecificationSignature(BlobReader signatureReader) | ||
| { | ||
| switch (signatureReader.ReadSignatureTypeCode()) |
There was a problem hiding this comment.
This check may be too restrictive after looking into this in more detail.
We have ECMA-335 spec augment that suggests that the only TypeSpec should only disallow CLASS and VALUETYPE since the other forms may show up in different contexts: https://github.com/dotnet/runtime/blob/main/docs/design/specs/Ecma-335-Augments.md#5-typespecs-can-encode-more-than-specified
I think we should implement the behavior from ECMA spec augment here, and also link to it so that future readers understand where it came from.
| { | ||
| ldarg.0 | ||
| box int32& | ||
| box int32* |
There was a problem hiding this comment.
These changes should be unnecessary with the other feedback
| if (resolvedBaseType.IsValueType || | ||
| (baseType.Kind == HandleKind.TypeSpecification && resolvedBaseType is not InstantiatedType)) |
There was a problem hiding this comment.
| if (resolvedBaseType.IsValueType || | |
| (baseType.Kind == HandleKind.TypeSpecification && resolvedBaseType is not InstantiatedType)) | |
| if (!resolvedBaseType.IsDefType || resolvedBaseType.IsInterface || resolvedBaseType.IsPrimitive) |
I think this would be more precise.
There was a problem hiding this comment.
Thanks, I updated this to use a more precise check.
I used resolvedBaseType.IsValueType || resolvedBaseType.IsInterface || !resolvedBaseType.IsDefType because base types should reject value types in general, not just primitive types. IsValueType also covers the primitive case.
| // Once the base type metadata is invalid, later checks can force the type system to | ||
| // resolve the same bad base token again and produce a duplicate token resolution error. | ||
| if (!VerifyBaseType()) | ||
| { | ||
| return; | ||
| } |
There was a problem hiding this comment.
| // Once the base type metadata is invalid, later checks can force the type system to | |
| // resolve the same bad base token again and produce a duplicate token resolution error. | |
| if (!VerifyBaseType()) | |
| { | |
| return; | |
| } | |
| VerifyBaseType(); |
We may want to stop on errors thrown by the type system only (throw an exception that will be caught by the caller of Verify method) ,but keep going for non-fatal errors to produce as many of them as possible. It is how the existing VerifyInterfaces method seems to work.
|
Thanks for the review. I updated the PR to address the latest feedback, The TypeSpec validation now follows the ECMA-335 augment behavior. Please let me know if there is anything else I should adjust. |
|
(Edited to fix issue number) I wonder if we should approach this from an angle that also allows to address #4945
|
Yes, I think it would look better. @pkuyo Could you please implement it? |
| } | ||
| else | ||
| { | ||
| if (baseType.Kind == HandleKind.TypeSpecification && |
There was a problem hiding this comment.
This should not be needed. It is covered by the resolvedBaseType.IsValueType || resolvedBaseType.IsInterface || !resolvedBaseType.IsDefType check below.
There was a problem hiding this comment.
Thanks. I simplified this to a smaller TypeSpec-specific check.
The resolvedBaseType.IsValueType || resolvedBaseType.IsInterface || !resolvedBaseType.IsDefType check alone does not catch the #119536 case, because a TypeSpec that resolves to System.Object passes those resolved-type checks. The extra TypeSpec check is only for that context-specific extends validation.
| { | ||
| resolvedBaseType = _module.GetType(baseType); | ||
| } | ||
| catch (BadImageFormatException) |
There was a problem hiding this comment.
I think it would be better to let the errors from the core type system to propagate. Catching the exception and reporting it as something else loses information.
Sure, thanks. I’ll implement it this way and update the PR. |
Thanks, I reworked this to use the The TypeSpec validation is now done through For CMOD_OPT/CMOD_REQ, I kept the check simple for now: it reports a TypeSpec reference while parsing another TypeSpec, but does not try to distinguish whether it actually forms a cycle. Full cycle detection would need extra tracking for the active TypeSpec resolution stack, since the current TypeSpec is not in the cache yet. I wasn't sure that extra state was necessary for this PR, so I wanted to check before adding it. |
We shouldn't allow TypeSpec as a modifier. It only causes problems. If the augment says we allow it, we should update the augment. We can capture the reason so that somebody doesn't try to relax it without thinking about the consequences (@jkotas do you agree?). I think this recently came up in #123819 and we decided not to handle TypeSpec either, but GitHub makes it really hard to find the relevant comment. |
Fixes #119536
This adds ILVerify validation for invalid class base type declarations.
The original issue reports a case where ILAsm accepts a type declared with
extends object, but the resulting type cannot be loaded by the runtime.ILVerify previously accepted that assembly without reporting an error.
This change reports
InvalidBaseTypefor invalid base type declarations ratherthan special-casing only the exact
extends objectspelling.Tests added in
BaseTypeTests.il:ObjectTypeSpecBase_InvalidType_InvalidBaseTypeextends object.InvalidBaseType.NilBaseInterface_ValidType_ValidGenericClassTypeSpecBase_ValidType_ValidValueTypeBase_InvalidType_InvalidBaseTypeGenericValueTypeSpecBase_InvalidType_InvalidBaseType