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.
Summary
Parsing a JSON Schema document that contains a duplicate top-level key - the smallest case is 23 bytes,
{"items":{},"items":{}}- throws an uncaughtSystem.InvalidCastExceptionout ofNJsonSchema.ExtensionDataDeserializationConverter.ReadJson. The exception isInvalidCastExceptionrather than aJsonException, so consumers that wrapJsonSchema.FromJsonAsyncincatch (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
Exception
Where it appears to go wrong
From reading the source,
ExtensionDataDeserializationConverter.ReadJson(src/NJsonSchema/JsonExtensionObject.cs:31) does an unchecked(IJsonExtensionObject)Activator.CreateInstance(objectType)whileCanConvertreturnstruefor any type. On duplicate keys the converter appears to be re-entered withobjectType == 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.