Following code
XmlDocument xdoc = new XmlDocument();
String xml = @"<!DOCTYPE lolz [" +
"<!ENTITY lol \"lol\">" +
"<!ENTITY lol2 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">" +
"<!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">" +
"<!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">" +
"<!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">" +
"<!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">" +
"<!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">" +
"<!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">" +
"<!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">" +
"]>" +
"<lolz>&lol9;</lolz>";
xdoc.LoadXml(xml);
.Net 4.0 This code will throw exception The input document has exceeded a limit set by MaxCharactersFromEntities
.Net 2.0/3.5 This code will not throw any exception and will keep on growing in XML until memory limit is reached
Can somebody explain the reason of this difference?
Research done so far
I disassembled System.Xml v2.0 and v4.0 and only change I saw was in method RegisterConsumedCharacters
v2.0 definition
private void RegisterConsumedCharacters(long characters, bool inEntityReference)
{
if (this.maxCharactersInDocument > 0L)
{
long num = this.charactersInDocument + characters;
if (num < this.charactersInDocument)
{
this.ThrowWithoutLineInfo("XmlSerializeErrorDetails", new string[] { "MaxCharactersInDocument", "" });
}
else
{
this.charactersInDocument = num;
}
if (this.charactersInDocument > this.maxCharactersInDocument)
{
this.ThrowWithoutLineInfo("XmlSerializeErrorDetails", new string[] { "MaxCharactersInDocument", "" });
}
}
if ((this.maxCharactersFromEntities > 0L) && inEntityReference)
{
long num2 = this.charactersFromEntities + characters;
if (num2 < this.charactersFromEntities)
{
this.ThrowWithoutLineInfo("XmlSerializeErrorDetails", new string[] { "MaxCharactersFromEntities", "" });
}
else
{
this.charactersFromEntities = num2;
}
if ((this.charactersFromEntities > this.maxCharactersFromEntities) && XmlTextReaderSection.LimitCharactersFromEntities)
{
this.ThrowWithoutLineInfo("XmlSerializeErrorDetails", new string[] { "MaxCharactersFromEntities", "" });
}
}
}
v4.0 definition
private void RegisterConsumedCharacters(long characters, bool inEntityReference)
{
if (this.maxCharactersInDocument > 0L)
{
long num = this.charactersInDocument + characters;
if (num < this.charactersInDocument)
{
this.ThrowWithoutLineInfo("Xml_LimitExceeded", "MaxCharactersInDocument");
}
else
{
this.charactersInDocument = num;
}
if (this.charactersInDocument > this.maxCharactersInDocument)
{
this.ThrowWithoutLineInfo("Xml_LimitExceeded", "MaxCharactersInDocument");
}
}
if ((this.maxCharactersFromEntities > 0L) && inEntityReference)
{
long num2 = this.charactersFromEntities + characters;
if (num2 < this.charactersFromEntities)
{
this.ThrowWithoutLineInfo("Xml_LimitExceeded", "MaxCharactersFromEntities");
}
else
{
this.charactersFromEntities = num2;
}
if (this.charactersFromEntities > this.maxCharactersFromEntities)
{
this.ThrowWithoutLineInfo("Xml_LimitExceeded", "MaxCharactersFromEntities");
}
}
}
Only difference I see here is change in parameters of ThrowWithoutLineInfo and removal of XmlTextReaderSection.LimitCharactersFromEntities in v4.0, but I am not able to make much out of it and have hit a block here.
The default value for XmlReaderSettings.MaxCharactersFromEntities
is 0 and means "no limit" as MSDN documentation says.
But there is a nasty trick not pointed out by the documentation, in .net 4 if you don't pass a XmlReaderSettings
to your XmlTextReader
then the limit is not set to 0 but to 10,000,000.
The relevant source code is here, even with a comment pointing out that this is a breaking change: https://referencesource.microsoft.com/#System.Xml/System/Xml/Core/XmlTextReaderImpl.cs,385
So, you have found the culprit. In v2.0, XmlTextReaderSection.LimitCharactersFromEntities
returns false
so the exception is not thrown. In v4.0 XmlTextReaderSection.LimitCharactersFromEntities
is omitted from the condition check so it throws the exception.
The question is what does XmlTextReaderSection.LimitCharactersFromEntities
do and why does it return false
? When we disassemble it, we see that it does the following:
internal string LimitCharactersFromEntitiesString
{
get
{
return (string)base["limitCharactersFromEntities"];
}
set
{
base["limitCharactersFromEntities"] = value;
}
}
private bool _LimitCharactersFromEntities
{
get
{
string limitCharactersFromEntitiesString = this.LimitCharactersFromEntitiesString;
bool result = true;
XmlConvert.TryToBoolean(limitCharactersFromEntitiesString, out result);
return result;
}
}
internal static bool LimitCharactersFromEntities
{
get
{
XmlTextReaderSection xmlTextReaderSection = ConfigurationManager.GetSection(XmlConfigurationString.XmlTextReaderSectionPath) as XmlTextReaderSection;
return xmlTextReaderSection == null || xmlTextReaderSection._LimitCharactersFromEntities;
}
}
So internal static bool
function calls private bool
one. private bool
calls internal string
. What internal string
does is it tries to get the dictionary entry "limitCharactersFromEntities"
from ConfigurationSection
parent class. It probably returns null, so it cannot be casted to bool
(check XmlConvert.TryToBoolean
source) and in the end XmlTextReaderSection.LimitCharactersFromEntities
returns false.
I checked further into the source code, but I could not find how or where LimitCharactersFromEntities
configuration property is initialized in v2.0 (I couldn't check the whole .NET so what I did was CTRL + F
to search for that string). However I've seen that LimitCharactersFromEntities
is totally omitted in v4.0 and it doesn't exist as one of configuration property names in XmlConfiguration.cs
.
So I guess we can conclude that in v2.0, that's a bug.
Note: I have totally no idea about XmlDocument and the System.Xml namespace at all. I've just tried to make some educated guesses reading source code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With