Patch backported from Apache SVN for bug 272260. Index: test/data/billion-laughs.xml =================================================================== --- test/data/billion-laughs.xml (revision 0) +++ test/data/billion-laughs.xml (revision 781409) @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> +&laugh30; Property changes on: test/data/billion-laughs.xml ___________________________________________________________________ Added: svn:eol-style + native Index: test/testxml.c =================================================================== --- test/testxml.c (revision 781402) +++ test/testxml.c (revision 781409) @@ -36,8 +36,7 @@ return rv; rv = apr_file_puts("\n" - "\n", *fd); + "\n", *fd); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); for (i = 0; i < 5000; i++) { @@ -75,7 +74,7 @@ for (i = 0; i < 5000; i++) { rv = apr_file_puts("yummy\n", *fd); + "for=\"dinner <>=\">yummy\n", *fd); ABTS_INT_EQUAL(tc, APR_SUCCESS, rv); } @@ -103,7 +102,7 @@ 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 @@ 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, "data/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; } Index: xml/apr_xml.c =================================================================== --- xml/apr_xml.c (revision 781402) +++ xml/apr_xml.c (revision 781409) @@ -347,6 +347,25 @@ 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 @@ 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; }