diff options
author | Joe Orton <jorton@apache.org> | 2009-06-03 14:26:19 +0000 |
---|---|---|
committer | Joe Orton <jorton@apache.org> | 2009-06-03 14:26:19 +0000 |
commit | 8d22691a64c7b22af42b1a09227e3aad0fe90966 (patch) | |
tree | 943d82390d23ae82815415c1bce5c2afe2ed3a3c | |
parent | b0bf45a54d4619ecd2bd5d59e56eca275a3a8b97 (diff) | |
download | apr-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.xml | 36 | ||||
-rw-r--r-- | test/testxml.c | 25 | ||||
-rw-r--r-- | xml/apr_xml.c | 32 |
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 <>=\">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; } |