Skip to content

InvalidCastException in ExtensionDataDeserializationConverter.ReadJson on duplicate keys #1920

Description

@pawlos

Summary

Parsing a JSON Schema document that contains a duplicate top-level key - the smallest case is 23 bytes, {"items":{},"items":{}} - throws an uncaught System.InvalidCastException out of NJsonSchema.ExtensionDataDeserializationConverter.ReadJson. The exception is InvalidCastException rather than a JsonException, so consumers that wrap JsonSchema.FromJsonAsync in catch (Newtonsoft.Json.JsonException) won't catch it.

(RFC 8259 §4 says object names SHOULD be unique, not MUST, and Newtonsoft.Json accepts duplicates with last-wins semantics - so the input reaches NJsonSchema's converter as a parsed document either way. Whether to ultimately accept or reject such schemas is a design call for NJsonSchema;)

Version

Reproduced on NJsonSchema 11.6.0 (NuGet and current main).

Minimal repro

using NJsonSchema;

await JsonSchema.FromJsonAsync("""{"items":{},"items":{}}""");

Exception

System.InvalidCastException: Unable to cast object of type 'System.Object' to type 'NJsonSchema.IJsonExtensionObject'.
   at NJsonSchema.ExtensionDataDeserializationConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(...)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(...)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(...)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Populate(...)
   at Newtonsoft.Json.Serialization.JsonSerializerProxy.PopulateInternal(...)
   at Newtonsoft.Json.JsonSerializer.Populate(...)
   at NJsonSchema.ExtensionDataDeserializationConverter.ReadJson(...)        ← outer frame
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](...)
   at NJsonSchema.Infrastructure.JsonSchemaSerialization.FromJson[T](...)
   at NJsonSchema.Infrastructure.JsonSchemaSerialization.FromJsonWithLoaderAsync[T](...)

Where it appears to go wrong

From reading the source, ExtensionDataDeserializationConverter.ReadJson (src/NJsonSchema/JsonExtensionObject.cs:31) does an unchecked (IJsonExtensionObject)Activator.CreateInstance(objectType) while CanConvert returns true for any type. On duplicate keys the converter appears to be re-entered with objectType == typeof(object), at which point the cast throws. Happy to dig further if that's useful.

Context

Found via an AFL++/SharpFuzz fuzzing harness targeting JsonSchema.FromJsonAsync. Original fuzzer output was 142 bytes; minimized by hand to 23 bytes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions