summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Orton <jorton@apache.org>2009-06-03 14:26:19 +0000
committerJoe Orton <jorton@apache.org>2009-06-03 14:26:19 +0000
commit8d22691a64c7b22af42b1a09227e3aad0fe90966 (patch)
tree943d82390d23ae82815415c1bce5c2afe2ed3a3c
parentb0bf45a54d4619ecd2bd5d59e56eca275a3a8b97 (diff)
downloadapr-8d22691a64c7b22af42b1a09227e3aad0fe90966.tar.gz
Prevent "billion laughs" attack against expat:
* xml/apr_xml.c (entity_declaration, default_handler): Add new handlers for expat 2.x and 1.x respectively. (apr_xml_parser_create): Install handler to prevent expansion of internal entities with expat 1.x, and to fail on an entity declaration with expat 2.x. * test/testxml.c (create_dummy_file, dump_xml): Test that predefined entities are expanded. (test_billion_laughs): New test case. git-svn-id: https://svn.apache.org/repos/asf/apr/apr/trunk@781403 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--test/data/billion-laughs.xml36
-rw-r--r--test/testxml.c25
-rw-r--r--xml/apr_xml.c32
3 files changed, 89 insertions, 4 deletions
diff --git a/test/data/billion-laughs.xml b/test/data/billion-laughs.xml
new file mode 100644
index 000000000..3af89ae4d
--- /dev/null
+++ b/test/data/billion-laughs.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!DOCTYPE billion [
+<!ELEMENT billion (#PCDATA)>
+<!ENTITY laugh0 "ha">
+<!ENTITY laugh1 "&laugh0;&laugh0;">
+<!ENTITY laugh2 "&laugh1;&laugh1;">
+<!ENTITY laugh3 "&laugh2;&laugh2;">
+<!ENTITY laugh4 "&laugh3;&laugh3;">
+<!ENTITY laugh5 "&laugh4;&laugh4;">
+<!ENTITY laugh6 "&laugh5;&laugh5;">
+<!ENTITY laugh7 "&laugh6;&laugh6;">
+<!ENTITY laugh8 "&laugh7;&laugh7;">
+<!ENTITY laugh9 "&laugh8;&laugh8;">
+<!ENTITY laugh10 "&laugh9;&laugh9;">
+<!ENTITY laugh11 "&laugh10;&laugh10;">
+<!ENTITY laugh12 "&laugh11;&laugh11;">
+<!ENTITY laugh13 "&laugh12;&laugh12;">
+<!ENTITY laugh14 "&laugh13;&laugh13;">
+<!ENTITY laugh15 "&laugh14;&laugh14;">
+<!ENTITY laugh16 "&laugh15;&laugh15;">
+<!ENTITY laugh17 "&laugh16;&laugh16;">
+<!ENTITY laugh18 "&laugh17;&laugh17;">
+<!ENTITY laugh19 "&laugh18;&laugh18;">
+<!ENTITY laugh20 "&laugh19;&laugh19;">
+<!ENTITY laugh21 "&laugh20;&laugh20;">
+<!ENTITY laugh22 "&laugh21;&laugh21;">
+<!ENTITY laugh23 "&laugh22;&laugh22;">
+<!ENTITY laugh24 "&laugh23;&laugh23;">
+<!ENTITY laugh25 "&laugh24;&laugh24;">
+<!ENTITY laugh26 "&laugh25;&laugh25;">
+<!ENTITY laugh27 "&laugh26;&laugh26;">
+<!ENTITY laugh28 "&laugh27;&laugh27;">
+<!ENTITY laugh29 "&laugh28;&laugh28;">
+<!ENTITY laugh30 "&laugh29;&laugh29;">
+]>
+<billion>&laugh30;</billion>
diff --git a/test/testxml.c b/test/testxml.c
index 8f511f3fc..9cde9f5c1 100644
--- a/test/testxml.c
+++ b/test/testxml.c
@@ -36,8 +36,7 @@ static apr_status_t create_dummy_file_error(abts_case *tc, apr_pool_t *p,
return rv;
rv = apr_file_puts("<?xml version=\"1.0\" ?>\n<maryx>"
- "<had a=\"little\"/><lamb its='fleece "
- "was white as snow' />\n", *fd);
+ "<had a=\"little\"/><lamb/>\n", *fd);
ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
for (i = 0; i < 5000; i++) {
@@ -75,7 +74,7 @@ static apr_status_t create_dummy_file(abts_case *tc, apr_pool_t *p,
for (i = 0; i < 5000; i++) {
rv = apr_file_puts("<hmm roast=\"lamb\" "
- "for=\"dinner\">yummy</hmm>\n", *fd);
+ "for=\"dinner &lt;&gt;&#x3D;\">yummy</hmm>\n", *fd);
ABTS_INT_EQUAL(tc, APR_SUCCESS, rv);
}
@@ -103,7 +102,7 @@ static void dump_xml(abts_case *tc, apr_xml_elem *e, int level)
a = e->attr;
ABTS_PTR_NOTNULL(tc, a);
ABTS_STR_EQUAL(tc, "for", a->name);
- ABTS_STR_EQUAL(tc, "dinner", a->value);
+ ABTS_STR_EQUAL(tc, "dinner <>=", a->value);
a = a->next;
ABTS_PTR_NOTNULL(tc, a);
ABTS_STR_EQUAL(tc, "roast", a->name);
@@ -149,11 +148,29 @@ static void test_xml_parser(abts_case *tc, void *data)
ABTS_TRUE(tc, rv != APR_SUCCESS);
}
+static void test_billion_laughs(abts_case *tc, void *data)
+{
+ apr_file_t *fd;
+ apr_xml_parser *parser;
+ apr_xml_doc *doc;
+ apr_status_t rv;
+
+ rv = apr_file_open(&fd, "billion-laughs.xml",
+ APR_FOPEN_READ, 0, p);
+ apr_assert_success(tc, "open billion-laughs.xml", rv);
+
+ rv = apr_xml_parse_file(p, &parser, &doc, fd, 2000);
+ ABTS_TRUE(tc, rv != APR_SUCCESS);
+
+ apr_file_close(fd);
+}
+
abts_suite *testxml(abts_suite *suite)
{
suite = ADD_SUITE(suite);
abts_run_test(suite, test_xml_parser, NULL);
+ abts_run_test(suite, test_billion_laughs, NULL);
return suite;
}
diff --git a/xml/apr_xml.c b/xml/apr_xml.c
index 256223f44..b3ec87578 100644
--- a/xml/apr_xml.c
+++ b/xml/apr_xml.c
@@ -347,6 +347,25 @@ static apr_status_t cleanup_parser(void *ctx)
return APR_SUCCESS;
}
+#if XML_MAJOR_VERSION > 1
+/* Stop the parser if an entity declaration is hit. */
+static void entity_declaration(void *userData, const XML_Char *entityName,
+ int is_parameter_entity, const XML_Char *value,
+ int value_length, const XML_Char *base,
+ const XML_Char *systemId, const XML_Char *publicId,
+ const XML_Char *notationName)
+{
+ apr_xml_parser *parser = userData;
+
+ XML_StopParser(parser->xp, XML_FALSE);
+}
+#else
+/* A noop default_handler. */
+static void default_handler(void *userData, const XML_Char *s, int len)
+{
+}
+#endif
+
APU_DECLARE(apr_xml_parser *) apr_xml_parser_create(apr_pool_t *pool)
{
apr_xml_parser *parser = apr_pcalloc(pool, sizeof(*parser));
@@ -372,6 +391,19 @@ APU_DECLARE(apr_xml_parser *) apr_xml_parser_create(apr_pool_t *pool)
XML_SetElementHandler(parser->xp, start_handler, end_handler);
XML_SetCharacterDataHandler(parser->xp, cdata_handler);
+ /* Prevent the "billion laughs" attack against expat by disabling
+ * internal entity expansion. With 2.x, forcibly stop the parser
+ * if an entity is declared - this is safer and a more obvious
+ * failure mode. With older versions, installing a noop
+ * DefaultHandler means that internal entities will be expanded as
+ * the empty string, which is also sufficient to prevent the
+ * attack. */
+#if XML_MAJOR_VERSION > 1
+ XML_SetEntityDeclHandler(parser->xp, entity_declaration);
+#else
+ XML_SetDefaultHandler(parser->xp, default_handler);
+#endif
+
return parser;
}