StackOverflowException when calling XmlElement.OuterXml on deeply nested xml
I am having a problem validating some XML input data on my WebService. I am including everything you should need to reproduce the error, so hopefully someone will be able to help me. Thank you in advance!
I have some hacker who is sending me some invalid XML to my public interface and instead of being detected in my XSD validation, it crashes the entire IIS WebApp before it even gets to the XSD, and I CAN'T GET AROUND IT! Here is the XML that he sends:
<?xml version="1.0"?>
<Security xmlns="">
<SecurityPermissions>
<SecurityPermission ActionID="90" Value="1"></SecurityPermission>
<SecurityPermission ActionID="80" Value="1"></SecurityPermission>
</SecurityPermissions>
<C><C><C><C><C>...</C></C></C></C></C>
</Security>
The thing that is invalid is the nested <C> nodes. I have shortened the XML here, but they are like 1000 levels deep.
For the full XML, please download it HERE.
For the XSD file, please download it HERE.
Here is the XSD schema:
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema targetNamespace="UpdatePermissions.xsd" xmlns="UpdatePermissions.xsd" xmlns:mstns="UpdatePermissions.xsd" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" >
<xs:element name="Security">
<xs:complexType>
<xs:sequence>
<xs:element name="SecurityPermissions" minOccurs="1" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="SecurityPermission" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="ActionID" type="xs:int" use="required" />
<xs:attribute name="Value" type="xs:int" use="required" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
IMPORTANT POINT:
Since the XML input has xmlns="" (and this is how ALL of my clients pass it in), I am forced to modify the inputted XML with a new xmlns va开发者_Go百科lue (I set it to xmlns="UpdatePermissions.xsd") in order for the XSD validation to work. Since you can't just set the xmlns on an existing XmlElement, I have to copy the XML to a XmlDocumentFragment. This - along with the nested nodes - is crashing my app with a StackOverflowException which can't be caught in a try..catch. Here's what it looks like:
[WebMethod]
public XmlElement UpdatePermissions(XmlElement xInput)
{
var errors = ValidateUpdatePermissionsXml(xInput);
if (errors.Count > 0)
throw new Exception(" I hate everything");
// do other processing
return xInput;
}
public List<string> ValidateUpdatePermissionsXml(XmlElement xInput)
{
// set xmlns since it isn't provided by caller but we need it to validate against XSD
xInput.SetAttribute("xmlns", "UpdatePermissions.xsd");
// transfer XML to a fragment otherwise xmlns attribute we set wouldn't take effect
var fragment = xInput.OwnerDocument.CreateDocumentFragment();
var xmlString = xInput.OuterXml; // <---- THIS IS WHERE THE APP CRASHES
fragment.InnerXml = xmlString;
//setup errors array
var errors = new List<string>();
//read in schema
var schemaStream = File.OpenRead(@"c:\UpdatePermissions.xsd");
var schema = XmlSchema.Read(schemaStream, null);
var schemaSet = new XmlSchemaSet();
schemaSet.Add(schema);
//setup validation settings
var readerSettings = new XmlReaderSettings();
readerSettings.ValidationType = ValidationType.Schema;
readerSettings.Schemas = schemaSet;
readerSettings.ValidationEventHandler += (s, e) => errors.Add(e.Exception.Message);
//create readers
var nodeReader = new XmlNodeReader(fragment);
var reader = XmlReader.Create(nodeReader, readerSettings);
//read
while (reader.Read()) ;
return errors;
}
Here is my unit test which always fails:
[TestMethod]
public void TestXSDValidation_Invalid_DeeplyNestedNodes()
{
//load Xml from file into XmlElement
var badXml = File.ReadAllText(@"c:\UpdatePermissions_BadRequest.xml");
var doc = new XmlDocument();
doc.LoadXml(badXml);
var badElement = doc.DocumentElement;
var errors = ValidateUpdatePermissionsXml(badElement);
Assert.AreEqual(1, errors.Count);
Assert.AreEqual("The element 'SecurityPermissions' in namespace 'UpdatePermissions.xsd' has invalid child element 'C' in namespace 'UpdatePermissions.xsd'. List of possible elements expected: 'SecurityPermission' in namespace 'UpdatePermissions.xsd'.", errors[0]);
}
It appears that the call to .OuterXml fails with a StackOverflowException because of how deeply the nodes are nested. All of my attempts to write the XML to a string by other means have failed as well.
The Problem
What I can't figure out is how I can accept the XML which has the EMPTY xmlns attribute value, and still do the validation without having to copy the XML to the fragment. Everything I have tried that writes the XML out in some way fails and crashes the app. Try it and you will see what I mean!
Rather than attempt to convert the entire request to a string why not parse the xml using an XmlTextReader of the request stream? There's an article providing more detail of this class here:
http://support.microsoft.com/kb/307548
精彩评论