[CSHARP-4567] Support projections where the lambda body returns a type different from but assignable to the lambda return type Created: 13/Mar/23  Updated: 28/Oct/23  Resolved: 17/May/23

Status: Closed
Project: C# Driver
Component/s: LINQ3, Serialization
Affects Version/s: 2.19.0
Fix Version/s: 2.19.2

Type: Bug Priority: Major - P3
Reporter: Tom Murphy Assignee: Robert Stam
Resolution: Fixed Votes: 1
Labels: Bug, triage
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: File MongoDB.Projection.Test.7z     PNG File image-2023-05-15-10-00-22-523.png    
Backwards Compatibility: Fully Compatible
Documentation Changes: Not Needed
Documentation Changes Summary:

1. What would you like to communicate to the user about this feature?
2. Would you like the user to see examples of the syntax and/or executable code and its output?
3. Which versions of the driver/connector does this apply to?


 Description   

Summary

Projection LINQ expressions dynamically created throw InvalidCastException when the projection is applied to IFluentFind and executed in C# driver version 2.19.0.

Exception Information:

Unable to cast object of type 'MongoDB.Bson.Serialization.BsonClassMapSerializer`1[DynamicType0`2]' to type 'MongoDB.Bson.Serialization.IBsonSerializer`1[System.Object]'.

   at MongoDB.Driver.Linq.Linq3Implementation.LinqProviderAdapterV3.TranslateExpressionToProjection[TInput,TOutput](Expression`1 expression, IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions)
   at MongoDB.Driver.Linq.Linq3Implementation.LinqProviderAdapterV3.TranslateExpressionToFindProjection[TSource,TProjection](Expression`1 expression, IBsonSerializer`1 sourceSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.MongoCollectionImpl`1.CreateFindOperation[TProjection](FilterDefinition`1 filter, FindOptions`2 options)
   at MongoDB.Driver.MongoCollectionImpl`1.FindAsync[TProjection](IClientSessionHandle session, FilterDefinition`1 filter, FindOptions`2 options, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.<>c_DisplayClass48_0`1.<FindAsync>b_0(IClientSessionHandle session)
   at MongoDB.Driver.MongoCollectionImpl`1.<UsingImplicitSessionAsync>d__107`1.MoveNext()
   at MongoDB.Driver.IAsyncCursorSourceExtensions.<ToListAsync>d__16`1.MoveNext()
   at Program.<<Main>$>d__0.MoveNext() in C:\...\repos\MongoDB.Projection.Test\MongoDB.Projection.Test\Program.cs:line 52

How to Reproduce

Download the attached VS solution and run the project. The code contains comments with information regarding the issue.

Additional Background

The projection is created using a dynamically generated System.Type via IL (see attached solution). The type is similar to an anonymous type containing a subset of the actual properties defined on a specific type. The properties can be any combination of properties from the source type and are verified against the source type preventing invalid properties from being added to the projection. Once the projection Type is generated it is then used in the creation of a dynamic LINQ expression that represents the actual projection applied to the IFluentFind.

As mentioned, the projection is similar to a projection created using an anonymous type. The anonymous type projection does not throw the exception though.

The implementation described here has been in use for quite some time in our systems and has worked quite well in dozens of microservices deployed in the US and Europe. The issue was only realized recently when we updated to driver version v2.19.0. This implementation is part of a larger framework that is also used outside of MongoDB so this is a bit of a critical issue.

The issue is related to the changes made to address MongoDB .NET/C# Driver vulnerable to Deserialization of Untrusted Data · CVE-2022-48282 · GitHub Advisory Database · GitHub I assume but it isn't clear if this change was intended to prevent the scenario described in our implementation. Based on the comments made by James_Kovacs here Vulnerability CVE-2022-48282 - Working with Data / Drivers & ODMs - MongoDB Developer Community Forums it seems like it should not.



 Comments   
Comment by Githook User [ 18/May/23 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4567: Support implicit conversion when lambda body returns a type assignable to the lambda return type.
Branch: v2.19.x
https://github.com/mongodb/mongo-csharp-driver/commit/3c2d4ff52aa576fe2ccf1a3033ac65700327e8b8

Comment by Robert Stam [ 17/May/23 ]

While the repro provided is rather complex (it generates expressions dynamically), the root cause was found to be a projection like this:

Expression<Func<C, object>> projection = x => new R { X = x.Id };
 
var find = collection.Find("{}").Project(projection);

The lambda is declared to return `object` but the body returns a subclass of `object`.

We were expecting the body to return a value whose type was the same as the return type of the lambda. We now support the body returning a value that is different from the lambda return type, as long as it is assignable to the lambda return type (which it must be, or the code would not have compiled in the first place).

Comment by Githook User [ 17/May/23 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4567: Support implicit conversion when lambda body returns a type assignable to the lambda return type.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/b6190b5063f07c8639bc62e935d606da97a5d152

Comment by Inclouds N/A [ 15/May/23 ]

I can confirm that the problem appeared also for us, after upgrading to 2.19.1 version. 
Here is the repro code:

Comment by Robert Stam [ 12/May/23 ]

Sorry for the delay in investigating this. It shouldn't matter whether the projection expression was generated by the compiler or by your own code. But it does make it a bit harder to figure out what is going on. Thanks for the solution you provided that reproduces the issue. It helped me figure out the root cause and how to reproduce it without using dynamic expression generation.

I'm working on a fix now.

Comment by Tom Murphy [ 09/May/23 ]

Is there any update? The fact that this issue is now unassigned is not encouraging. I have colleagues in Europe that have identified additional issues with projections when including them in aggregate pipelines in driver version 2.19.0 which is totally outside of my issue. I saw in your backlog that there are other people that have had projections broken because driver 2.19.0 is not backward compatible. Any information would be helpful for me to relay to my organization.

Generated at Wed Feb 07 21:48:37 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.