Skip to content

Commit

Permalink
Merge pull request #10806 from rouault/gmlas_nas_billion_laugh
Browse files Browse the repository at this point in the history
Make GMLAS and NAS drivers robust to XML billion laugh attack
  • Loading branch information
rouault authored Sep 18, 2024
2 parents 0178ae6 + 29d44e6 commit 8ffe534
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 0 deletions.
21 changes: 21 additions & 0 deletions autotest/ogr/data/gml/billionlaugh.gml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!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;">
]>
<ogr:FeatureCollection
gml:id="aFeatureCollection"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ billionlaugh.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml/3.2">
<lolz>&lol9;</lolz>
</ogr:FeatureCollection>
69 changes: 69 additions & 0 deletions autotest/ogr/data/gml/billionlaugh.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
targetNamespace="http://ogr.maptools.org/"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:gml="http://www.opengis.net/gml/3.2"
xmlns:gmlsf="http://www.opengis.net/gmlsf/2.0"
elementFormDefault="qualified"
version="1.0">
<xs:annotation>
<xs:appinfo source="http://schemas.opengis.net/gmlsfProfile/2.0/gmlsfLevels.xsd">
<gmlsf:ComplianceLevel>0</gmlsf:ComplianceLevel>
</xs:appinfo>
</xs:annotation>
<xs:import namespace="http://www.opengis.net/gml/3.2" schemaLocation="http://schemas.opengis.net/gml/3.2.1/gml.xsd"/>
<xs:import namespace="http://www.opengis.net/gmlsf/2.0" schemaLocation="http://schemas.opengis.net/gmlsfProfile/2.0/gmlsfLevels.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence minOccurs="0" maxOccurs="unbounded">
<xs:element name="featureMember">
<xs:complexType>
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureMemberType">
<xs:sequence>
<xs:element ref="gml:AbstractFeature"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="poly" type="ogr:poly_Type" substitutionGroup="gml:AbstractFeature"/>
<xs:complexType name="poly_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:SurfacePropertyType" nillable="true" minOccurs="0" maxOccurs="1"/> <!-- restricted to Polygon --><!-- srsName="urn:ogc:def:crs:EPSG::27700" -->
<xs:element name="AREA" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
<xs:totalDigits value="11"/>
<xs:fractionDigits value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="EAS_ID" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:long">
<xs:totalDigits value="11"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="PRFEDEA" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="16"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
45 changes: 45 additions & 0 deletions autotest/ogr/data/nas/billionlaugh.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!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;">
]>
<!--Die NAS-Datei wurde mit der 3A-Version 6.0.9.3 erstellt.-->
<AX_NutzerbezogeneBestandsdatenaktualisierung_NBA xsi:schemaLocation="http://www.adv-online.de/namespaces/adv/gid/6.0 NAS-Operationen.xsd" xmlns="http://www.adv-online.de/namespaces/adv/gid/6.0" xmlns:gsr="http://www.isotc211.org/2005/gsr" xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:fc="http://www.adv-online.de/namespaces/adv/gid/fc/6.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:kom="http://www.lverma.nrw.de/namespaces/geobasis/3.5" xmlns:wfsext="http://www.adv-online.de/namespaces/adv/gid/wfsext" xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gts="http://www.isotc211.org/2005/gts" xmlns:ogc="http://www.adv-online.de/namespaces/adv/gid/ogc" xmlns:wfs="http://www.adv-online.de/namespaces/adv/gid/wfs" xmlns:gss="http://www.isotc211.org/2005/gss">
<lolz>&lol9;</lolz>
<erlaeuterung>&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;Protocol xmlns="http://www.aed-sicad.de/namespaces/va" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;&lt;auftragsid&gt;AMGR000000161077&lt;/auftragsid&gt;&lt;profilkennung&gt;NBABenutzerLA&lt;/profilkennung&gt;&lt;auftragsnummer&gt;Admin_100413082150_001_0006&lt;/auftragsnummer&gt;&lt;antragsnummer&gt;Admin_100413082150_0006&lt;/antragsnummer&gt;&lt;nasoperation&gt;NutzerbezogeneBestandsdatenaktualisierung_NBA&lt;/nasoperation&gt;&lt;status&gt;beendet&lt;/status&gt;&lt;startzeitreal&gt;2010-10-13T22:29:43.66646+02:00&lt;/startzeitreal&gt;&lt;endzeitreal&gt;2010-10-13T22:29:43.66646+02:00&lt;/endzeitreal&gt;&lt;Message&gt;&lt;Category&gt;NOTHING&lt;/Category&gt;&lt;MessageLevel&gt;Info&lt;/MessageLevel&gt;&lt;MessageObject&gt;&lt;FeatureClass /&gt;&lt;Id /&gt;&lt;/MessageObject&gt;&lt;MessageText&gt;Das Ergebnis wurde mit der 3A-Version 6.0.9.3 erstellt.&lt;/MessageText&gt;&lt;ProcessingTime&gt;2010-10-13T22:29:43.66646+02:00&lt;/ProcessingTime&gt;&lt;/Message&gt;&lt;/Protocol&gt;</erlaeuterung>
<erfolgreich>true</erfolgreich>
<antragsnummer>Admin_100413082150_0006</antragsnummer>
<allgemeineAngaben>
<AX_K_Benutzungsergebnis>
<folgeverarbeitung>
<AX_FOLGEVA />
</folgeverarbeitung>
<empfaenger>
<AA_Empfaenger>
<direkt>true</direkt>
</AA_Empfaenger>
</empfaenger>
</AX_K_Benutzungsergebnis>
</allgemeineAngaben>
<geaenderteObjekte>
<wfs:Transaction version="1.0.0" service="WFS" />
</geaenderteObjekte>
<auftragsnummer>Admin_100413082150_001_0006</auftragsnummer>
<portionskennung>
<AX_Portionskennung>
<profilkennung>NBABenutzerLA</profilkennung>
<datum>2010-10-13T20:10:14Z</datum>
<laufendeNummerVonGesamtzahl>16</laufendeNummerVonGesamtzahl>
<gesamtzahl>130</gesamtzahl>
<suedwestEcke>484560.000 5753705.000</suedwestEcke>
</AX_Portionskennung>
</portionskennung>
</AX_NutzerbezogeneBestandsdatenaktualisierung_NBA>
19 changes: 19 additions & 0 deletions autotest/ogr/ogr_gml.py
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,25 @@ def test_ogr_gml_64(parser):
assert feat is not None, parser


###############################################################################
# Test we don't spend too much time parsing documents featuring the billion
# laugh attack


@gdaltest.enable_exceptions()
@pytest.mark.parametrize("parser", ("XERCES", "EXPAT"))
def test_ogr_gml_billion_laugh(parser):

with gdal.config_option("GML_PARSER", parser), pytest.raises(
Exception, match="File probably corrupted"
):
with gdal.OpenEx("data/gml/billionlaugh.gml") as ds:
assert ds.GetDriver().GetDescription() == "GML"
for lyr in ds:
for f in lyr:
pass


###############################################################################
# Test SRSDIMENSION_LOC=GEOMETRY option (#5606)

Expand Down
16 changes: 16 additions & 0 deletions autotest/ogr/ogr_gmlas.py
Original file line number Diff line number Diff line change
Expand Up @@ -3485,3 +3485,19 @@ def test_ogr_gmlas_ossfuzz_70511():
Exception, match="Cannot get type definition for attribute y"
):
gdal.OpenEx("GMLAS:", open_options=["XSD=data/gmlas/test_ossfuzz_70511.xsd"])


###############################################################################
# Test we don't spend too much time parsing documents featuring the billion
# laugh attack


@gdaltest.enable_exceptions()
def test_ogr_gmlas_billion_laugh():

with gdal.quiet_errors(), pytest.raises(Exception, match="File probably corrupted"):
with gdal.OpenEx("GMLAS:data/gml/billionlaugh.gml") as ds:
assert ds.GetDriver().GetDescription() == "GMLAS"
for lyr in ds:
for f in lyr:
pass
14 changes: 14 additions & 0 deletions autotest/ogr/ogr_nas.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,17 @@ def test_ogr_nas_force_opening(tmp_vsimem):
with gdal.quiet_errors():
ds = gdal.OpenEx(filename, allowed_drivers=["NAS"])
assert ds.GetDriver().GetDescription() == "NAS"


###############################################################################
# Test we don't spend too much time parsing documents featuring the billion
# laugh attack


def test_ogr_nas_billion_laugh():

with gdal.config_option("NAS_GFS_TEMPLATE", ""):
with gdal.quiet_errors(), pytest.raises(
Exception, match="File probably corrupted"
):
ogr.Open("data/nas/billionlaugh.xml")
5 changes: 5 additions & 0 deletions ogr/ogrsf_frmts/gmlas/ogr_gmlas.h
Original file line number Diff line number Diff line change
Expand Up @@ -1792,6 +1792,9 @@ class GMLASReader final : public DefaultHandler
/** Stack of contexts to build XML tree of GML Geometry */
std::vector<NodeLastChild> m_apsXMLNodeStack{};

/** Counter used to prevent XML billion laugh attacks */
int m_nEntityCounter = 0;

/** Maximum allowed number of XML nesting level */
int m_nMaxLevel = 100;

Expand Down Expand Up @@ -2048,6 +2051,8 @@ class GMLASReader final : public DefaultHandler
virtual void characters(const XMLCh *const chars,
const XMLSize_t length) override;

void startEntity(const XMLCh *const name) override;

bool RunFirstPass(GDALProgressFunc pfnProgress, void *pProgressData,
bool bRemoveUnusedLayers, bool bRemoveUnusedFields,
bool bProcessSWEDataArray,
Expand Down
18 changes: 18 additions & 0 deletions ogr/ogrsf_frmts/gmlas/ogrgmlasreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,8 @@ void GMLASReader::startElement(const XMLCh *const uri,
,
const Attributes &attrs)
{
m_nEntityCounter = 0;

const CPLString &osLocalname(transcode(localname, m_osLocalname));
const CPLString &osNSURI(transcode(uri, m_osNSUri));
const CPLString &osNSPrefix(m_osNSPrefix = m_oMapURIToPrefix[osNSURI]);
Expand Down Expand Up @@ -2387,6 +2389,8 @@ void GMLASReader::endElement(const XMLCh *const uri,
#endif
)
{
m_nEntityCounter = 0;

m_nLevel--;

#ifdef DEBUG_VERBOSE
Expand Down Expand Up @@ -2675,6 +2679,20 @@ void GMLASReader::endElement(const XMLCh *const uri,
}
}

/************************************************************************/
/* startEntity() */
/************************************************************************/

void GMLASReader::startEntity(const XMLCh *const /* name */)
{
m_nEntityCounter++;
if (m_nEntityCounter > 1000 && !m_bParsingError)
{
throw SAXNotSupportedException(
"File probably corrupted (million laugh pattern)");
}
}

/************************************************************************/
/* SetSWEValue() */
/************************************************************************/
Expand Down
18 changes: 18 additions & 0 deletions ogr/ogrsf_frmts/nas/nashandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ void NASHandler::startElement(const XMLCh *const /* uri */,
const Attributes &attrs)

{
m_nEntityCounter = 0;

GMLReadState *poState = m_poReader->GetState();

transcode(localname, m_osElementName);
Expand Down Expand Up @@ -438,6 +440,8 @@ void NASHandler::endElement(const XMLCh *const /* uri */,
const XMLCh *const /* qname */)

{
m_nEntityCounter = 0;

GMLReadState *poState = m_poReader->GetState();

transcode(localname, m_osElementName);
Expand Down Expand Up @@ -662,6 +666,20 @@ void NASHandler::endElement(const XMLCh *const /* uri */,
}
}

/************************************************************************/
/* startEntity() */
/************************************************************************/

void NASHandler::startEntity(const XMLCh *const /* name */)
{
m_nEntityCounter++;
if (m_nEntityCounter > 1000 && !m_poReader->HasStoppedParsing())
{
throw SAXNotSupportedException(
"File probably corrupted (million laugh pattern)");
}
}

/************************************************************************/
/* characters() */
/************************************************************************/
Expand Down
7 changes: 7 additions & 0 deletions ogr/ogrsf_frmts/nas/nasreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ GMLFeature *NASReader::NextFeature()
CPLDebug("NAS", "Error during NextFeature()! Message:\n%s",
transcode(toCatch.getMessage()).c_str());
}
catch (const SAXException &toCatch)
{
CPLString osErrMsg;
transcode(toCatch.getMessage(), osErrMsg);
CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrMsg.c_str());
m_bStopParsing = true;
}

return poReturn;
}
Expand Down
4 changes: 4 additions & 0 deletions ogr/ogrsf_frmts/nas/nasreaderp.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class NASHandler final : public DefaultHandler

const Locator *m_Locator;

int m_nEntityCounter = 0;

public:
explicit NASHandler(NASReader *poReader);
virtual ~NASHandler();
Expand All @@ -101,6 +103,8 @@ class NASHandler final : public DefaultHandler

void fatalError(const SAXParseException &) override;

void startEntity(const XMLCh *const name) override;

void setDocumentLocator(const Locator *locator) override;

CPLString GetAttributes(const Attributes *attr);
Expand Down

0 comments on commit 8ffe534

Please sign in to comment.