On Fri, Sep 12, 2008 at 11:29:03PM -0400, Michael Gilbert wrote: > Package: libxml2 > Version: 2.6.32.dfsg-3 > Severity: grave > Tags: security > Justification: user security hole > > ubuntu just released a fix for a problem in libxml2 [1]. the issue appears > to currently be reserved [2], but since ubuntu has released a fix, other > distributions need to follow suit soon to limit the window of opportunity > for attacks. the description of the problem is > > It was discovered that libxml2 did not correctly handle long entity > names. If a user were tricked into processing a specially crafted XML > document, a remote attacker could execute arbitrary code with user > privileges or cause the application linked against libxml2 to crash, > leading to a denial of service. > > this likely affects all releases (stable, testing, and unstable). > > thanks for the hard work. > > [1] http://lwn.net/Articles/298282/ > [2] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-3529
FWIW, here is the patch. I'm not very much convinced by the look of it... Mike
* Unmerged path debian/changelog diff --git a/include/libxml/parser.h b/include/libxml/parser.h index 5d38f75..fa700bb 100644 --- a/include/libxml/parser.h +++ b/include/libxml/parser.h @@ -298,6 +298,7 @@ struct _xmlParserCtxt { xmlError lastError; xmlParserMode parseMode; /* the parser mode */ unsigned long nbentities; /* number of entities references */ + unsigned long sizeentities; /* size of parsed entities */ }; /** diff --git a/parser.c b/parser.c index 5d7324a..8ee5ca0 100644 --- a/parser.c +++ b/parser.c @@ -80,6 +80,95 @@ #include <zlib.h> #endif +static void +xmlFatalErr(xmlParserCtxtPtr ctxt, xmlParserErrors error, const char *info); + +/************************************************************************ + * * + * Arbitrary limits set in the parser. * + * * + ************************************************************************/ + +#define XML_PARSER_BIG_ENTITY 1000 +#define XML_PARSER_LOT_ENTITY 5000 + +/* + * XML_PARSER_NON_LINEAR is the threshold where the ratio of parsed entity + * replacement over the size in byte of the input indicates that you have + * and eponential behaviour. A value of 10 correspond to at least 3 entity + * replacement per byte of input. + */ +#define XML_PARSER_NON_LINEAR 10 + +/* + * xmlParserEntityCheck + * + * Function to check non-linear entity expansion behaviour + * This is here to detect and stop exponential linear entity expansion + * This is not a limitation of the parser but a safety + * boundary feature. + */ +static int +xmlParserEntityCheck(xmlParserCtxtPtr ctxt, unsigned long size, + xmlEntityPtr ent) +{ + unsigned long consumed = 0; + + if (ctxt == NULL) + return (0); + if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP) + return (1); + if (size != 0) { + /* + * Do the check based on the replacement size of the entity + */ + if (size < XML_PARSER_BIG_ENTITY) + return(0); + + /* + * A limit on the amount of text data reasonably used + */ + if (ctxt->input != NULL) { + consumed = ctxt->input->consumed + + (ctxt->input->cur - ctxt->input->base); + } + consumed += ctxt->sizeentities; + + if ((size < XML_PARSER_NON_LINEAR * consumed) && + (ctxt->nbentities * 3 < XML_PARSER_NON_LINEAR * consumed)) + return (0); + } else if (ent != NULL) { + /* + * use the number of parsed entities in the replacement + */ + size = ent->owner; + + /* + * The amount of data parsed counting entities size only once + */ + if (ctxt->input != NULL) { + consumed = ctxt->input->consumed + + (ctxt->input->cur - ctxt->input->base); + } + consumed += ctxt->sizeentities; + + /* + * Check the density of entities for the amount of data + * knowing an entity reference will take at least 3 bytes + */ + if (size * 3 < consumed * XML_PARSER_NON_LINEAR) + return (0); + } else { + /* + * strange we got no data for checking just return + */ + return (0); + } + + xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL); + return (1); +} + /** * xmlParserMaxDepth: * @@ -2301,6 +2390,7 @@ xmlParserHandlePEReference(xmlParserCtxtPtr ctxt) { */ #define growBuffer(buffer) { \ xmlChar *tmp; \ + buffer##_size += XML_PARSER_BUFFER_SIZE ; \ buffer##_size *= 2; \ tmp = (xmlChar *) \ xmlRealloc(buffer, buffer##_size * sizeof(xmlChar)); \ @@ -2344,7 +2434,7 @@ xmlStringLenDecodeEntities(xmlParserCtxtPtr ctxt, const xmlChar *str, int len, return(NULL); last = str + len; - if ((ctxt->depth > 40) || (ctxt->nbentities >= 500000)) { + if (ctxt->depth > 40) { xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL); return(NULL); } @@ -2384,7 +2474,6 @@ xmlStringLenDecodeEntities(xmlParserCtxtPtr ctxt, const xmlChar *str, int len, ent = xmlParseStringEntityRef(ctxt, &str); if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP) goto int_error; - ctxt->nbentities++; if (ent != NULL) ctxt->nbentities += ent->owner; if ((ent != NULL) && @@ -2409,6 +2498,10 @@ xmlStringLenDecodeEntities(xmlParserCtxtPtr ctxt, const xmlChar *str, int len, buffer[nbchars++] = *current++; if (nbchars > buffer_size - XML_PARSER_BUFFER_SIZE) { + if (xmlParserEntityCheck(ctxt, nbchars, ent)) { + xmlFree(rep); + goto int_error; + } growBuffer(buffer); } } @@ -2434,7 +2527,6 @@ xmlStringLenDecodeEntities(xmlParserCtxtPtr ctxt, const xmlChar *str, int len, ent = xmlParseStringPEReference(ctxt, &str); if (ctxt->lastError.code == XML_ERR_ENTITY_LOOP) goto int_error; - ctxt->nbentities++; if (ent != NULL) ctxt->nbentities += ent->owner; if (ent != NULL) { @@ -2452,6 +2544,10 @@ xmlStringLenDecodeEntities(xmlParserCtxtPtr ctxt, const xmlChar *str, int len, buffer[nbchars++] = *current++; if (nbchars > buffer_size - XML_PARSER_BUFFER_SIZE) { + if (xmlParserEntityCheck(ctxt, nbchars, ent)) { + xmlFree(rep); + goto int_error; + } growBuffer(buffer); } } @@ -3355,7 +3451,7 @@ xmlParseAttValueComplex(xmlParserCtxtPtr ctxt, int *attlen, int normalize) { * Just output the reference */ buf[len++] = '&'; - if (len > buf_size - i - 10) { + while (len > buf_size - i - 10) { growBuffer(buf); } for (;i > 0;i--) @@ -6209,11 +6305,6 @@ xmlParseReference(xmlParserCtxtPtr ctxt) { if (ent == NULL) return; if (!ctxt->wellFormed) return; - ctxt->nbentities++; - if (ctxt->nbentities >= 500000) { - xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL); - return; - } was_checked = ent->checked; if ((ent->name != NULL) && (ent->etype != XML_INTERNAL_PREDEFINED_ENTITY)) { @@ -6311,6 +6402,10 @@ xmlParseReference(xmlParserCtxtPtr ctxt) { xmlErrMsgStr(ctxt, XML_ERR_INTERNAL_ERROR, "invalid entity type found\n", NULL); } + /* + * Store the number of entities needing parsing for entity + * content and do checkings + */ if ((ent->owner != 0) || (ent->children == NULL)) { ent->owner = ctxt->nbentities - oldnbent; if (ent->owner == 0) @@ -6318,6 +6413,15 @@ xmlParseReference(xmlParserCtxtPtr ctxt) { } if (ret == XML_ERR_ENTITY_LOOP) { xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL); + xmlFreeNodeList(list); + return; + } + if (xmlParserEntityCheck(ctxt, 0, ent)) { + xmlFreeNodeList(list); + return; + } + if (ret == XML_ERR_ENTITY_LOOP) { + xmlFatalErr(ctxt, XML_ERR_ENTITY_LOOP, NULL); return; } else if ((ret == XML_ERR_OK) && (list != NULL)) { if (((ent->etype == XML_INTERNAL_GENERAL_ENTITY) || @@ -6372,6 +6476,8 @@ xmlParseReference(xmlParserCtxtPtr ctxt) { } else if (list != NULL) { xmlFreeNodeList(list); list = NULL; + } else if (ent->owner != 1) { + ctxt->nbentities += ent->owner; } } ent->checked = 1; @@ -6428,7 +6534,6 @@ xmlParseReference(xmlParserCtxtPtr ctxt) { } return; } - ctxt->nbentities += ent->owner; if ((ctxt->sax != NULL) && (ctxt->sax->reference != NULL) && (ctxt->replaceEntities == 0) && (!ctxt->disableSAX)) { /* @@ -6622,6 +6727,11 @@ xmlParseEntityRef(xmlParserCtxtPtr ctxt) { if (RAW == ';') { NEXT; /* + * Increase the number of entity references parsed + */ + ctxt->nbentities++; + + /* * Ask first SAX for entity resolution, otherwise try the * predefined set. */ @@ -6793,6 +6903,10 @@ xmlParseStringEntityRef(xmlParserCtxtPtr ctxt, const xmlChar ** str) { if (*ptr == ';') { ptr++; /* + * Increase the number of entity references parsed + */ + ctxt->nbentities++; + /* * Ask first SAX for entity resolution, otherwise try the * predefined set. */ @@ -6954,6 +7068,11 @@ xmlParsePEReference(xmlParserCtxtPtr ctxt) } else { if (RAW == ';') { NEXT; + /* + * Increase the number of entity references parsed + */ + ctxt->nbentities++; + if ((ctxt->sax != NULL) && (ctxt->sax->getParameterEntity != NULL)) entity = ctxt->sax->getParameterEntity(ctxt->userData, @@ -7164,6 +7283,11 @@ xmlParseStringPEReference(xmlParserCtxtPtr ctxt, const xmlChar **str) { if (cur == ';') { ptr++; cur = *ptr; + /* + * Increase the number of entity references parsed + */ + ctxt->nbentities++; + if ((ctxt->sax != NULL) && (ctxt->sax->getParameterEntity != NULL)) entity = ctxt->sax->getParameterEntity(ctxt->userData, @@ -11517,7 +11641,7 @@ xmlParseCtxtExternalEntity(xmlParserCtxtPtr ctx, const xmlChar *URL, if (ctx == NULL) return(-1); - if ((ctx->depth > 40) || (ctx->nbentities >= 500000)) { + if (ctx->depth > 40) { return(XML_ERR_ENTITY_LOOP); } @@ -11718,8 +11842,7 @@ xmlParseExternalEntityPrivate(xmlDocPtr doc, xmlParserCtxtPtr oldctxt, xmlChar start[4]; xmlCharEncoding enc; - if ((depth > 40) || - ((oldctxt != NULL) && (oldctxt->nbentities >= 500000))) { + if (depth > 40) { return(XML_ERR_ENTITY_LOOP); } @@ -11857,6 +11980,25 @@ xmlParseExternalEntityPrivate(xmlDocPtr doc, xmlParserCtxtPtr oldctxt, } ret = XML_ERR_OK; } + + /* + * Record in the parent context the number of entities replacement + * done when parsing that reference. + */ + oldctxt->nbentities += ctxt->nbentities; + /* + * Also record the size of the entity parsed + */ + if (ctxt->input != NULL) { + oldctxt->sizeentities += ctxt->input->consumed; + oldctxt->sizeentities += (ctxt->input->cur - ctxt->input->base); + } + /* + * And record the last error if any + */ + if (ctxt->lastError.code != XML_ERR_OK) + xmlCopyError(&ctxt->lastError, &oldctxt->lastError); + if (sax != NULL) ctxt->sax = oldsax; oldctxt->node_seq.maximum = ctxt->node_seq.maximum; @@ -11963,7 +12105,7 @@ xmlParseBalancedChunkMemoryInternal(xmlParserCtxtPtr oldctxt, int size; xmlParserErrors ret = XML_ERR_OK; - if ((oldctxt->depth > 40) || (oldctxt->nbentities >= 500000)) { + if (oldctxt->depth > 40) { return(XML_ERR_ENTITY_LOOP); } @@ -12087,7 +12229,17 @@ xmlParseBalancedChunkMemoryInternal(xmlParserCtxtPtr oldctxt, ctxt->myDoc->last = last; } + /* + * Record in the parent context the number of entities replacement + * done when parsing that reference. + */ oldctxt->nbentities += ctxt->nbentities; + /* + * Also record the last error if any + */ + if (ctxt->lastError.code != XML_ERR_OK) + xmlCopyError(&ctxt->lastError, &oldctxt->lastError); + ctxt->sax = oldsax; ctxt->dict = NULL; ctxt->attsDefault = NULL; @@ -13404,6 +13556,7 @@ xmlCtxtReset(xmlParserCtxtPtr ctxt) ctxt->charset = XML_CHAR_ENCODING_UTF8; ctxt->catalogs = NULL; ctxt->nbentities = 0; + ctxt->sizeentities = 0; xmlInitNodeInfoSeq(&ctxt->node_seq); if (ctxt->attsDefault != NULL) {