From 2554a2408e09f13652049e5ffb0d26196b02ebab Mon Sep 17 00:00:00 2001 From: Nick Wellnhofer Date: Tue, 8 Mar 2022 20:10:02 +0100 Subject: [PATCH] [CVE-2022-29824] Fix integer overflows In several places, the code handling string buffers didn't check for integer overflow or used wrong types for buffer sizes. This could result in out-of-bounds writes or other memory errors when working on large, multi-gigabyte buffers. Thanks to Felix Wilhelm for the report. --- buf.c | 86 +++++++++++++++++++++++----------------------------------- tree.c | 72 ++++++++++++++++++------------------------------ 2 files changed, 61 insertions(+), 97 deletions(-) diff --git a/buf.c b/buf.c index 24368d37..40a5ee06 100644 --- a/buf.c +++ b/buf.c @@ -30,6 +30,10 @@ #include /* for XML_MAX_TEXT_LENGTH */ #include "buf.h" +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t) -1) +#endif + #define WITH_BUFFER_COMPAT /** @@ -156,6 +160,8 @@ xmlBufPtr xmlBufCreateSize(size_t size) { xmlBufPtr ret; + if (size == SIZE_MAX) + return(NULL); ret = (xmlBufPtr) xmlMalloc(sizeof(xmlBuf)); if (ret == NULL) { xmlBufMemoryError(NULL, "creating buffer"); @@ -166,8 +172,8 @@ xmlBufCreateSize(size_t size) { ret->error = 0; ret->buffer = NULL; ret->alloc = xmlBufferAllocScheme; - ret->size = (size ? size+2 : 0); /* +1 for ending null */ - ret->compat_size = (int) ret->size; + ret->size = (size ? size + 1 : 0); /* +1 for ending null */ + ret->compat_size = (ret->size > INT_MAX ? INT_MAX : ret->size); if (ret->size){ ret->content = (xmlChar *) xmlMallocAtomic(ret->size * sizeof(xmlChar)); if (ret->content == NULL) { @@ -442,23 +448,17 @@ xmlBufGrowInternal(xmlBufPtr buf, size_t len) { CHECK_COMPAT(buf) if (buf->alloc == XML_BUFFER_ALLOC_IMMUTABLE) return(0); - if (buf->use + len < buf->size) + if (len < buf->size - buf->use) return(buf->size - buf->use); + if (len > SIZE_MAX - buf->use) + return(0); - /* - * Windows has a BIG problem on realloc timing, so we try to double - * the buffer size (if that's enough) (bug 146697) - * Apparently BSD too, and it's probably best for linux too - * On an embedded system this may be something to change - */ -#if 1 - if (buf->size > (size_t) len) - size = buf->size * 2; - else - size = buf->use + len + 100; -#else - size = buf->use + len + 100; -#endif + if (buf->size > (size_t) len) { + size = buf->size > SIZE_MAX / 2 ? SIZE_MAX : buf->size * 2; + } else { + size = buf->use + len; + size = size > SIZE_MAX - 100 ? SIZE_MAX : size + 100; + } if (buf->alloc == XML_BUFFER_ALLOC_BOUNDED) { /* @@ -744,7 +744,7 @@ xmlBufIsEmpty(const xmlBufPtr buf) int xmlBufResize(xmlBufPtr buf, size_t size) { - unsigned int newSize; + size_t newSize; xmlChar* rebuf = NULL; size_t start_buf; @@ -772,9 +772,13 @@ xmlBufResize(xmlBufPtr buf, size_t size) case XML_BUFFER_ALLOC_IO: case XML_BUFFER_ALLOC_DOUBLEIT: /*take care of empty case*/ - newSize = (buf->size ? buf->size*2 : size + 10); + if (buf->size == 0) { + newSize = (size > SIZE_MAX - 10 ? SIZE_MAX : size + 10); + } else { + newSize = buf->size; + } while (size > newSize) { - if (newSize > UINT_MAX / 2) { + if (newSize > SIZE_MAX / 2) { xmlBufMemoryError(buf, "growing buffer"); return 0; } @@ -782,15 +786,15 @@ xmlBufResize(xmlBufPtr buf, size_t size) } break; case XML_BUFFER_ALLOC_EXACT: - newSize = size+10; + newSize = (size > SIZE_MAX - 10 ? SIZE_MAX : size + 10); break; case XML_BUFFER_ALLOC_HYBRID: if (buf->use < BASE_BUFFER_SIZE) newSize = size; else { - newSize = buf->size * 2; + newSize = buf->size; while (size > newSize) { - if (newSize > UINT_MAX / 2) { + if (newSize > SIZE_MAX / 2) { xmlBufMemoryError(buf, "growing buffer"); return 0; } @@ -800,7 +804,7 @@ xmlBufResize(xmlBufPtr buf, size_t size) break; default: - newSize = size+10; + newSize = (size > SIZE_MAX - 10 ? SIZE_MAX : size + 10); break; } @@ -866,7 +870,7 @@ xmlBufResize(xmlBufPtr buf, size_t size) */ int xmlBufAdd(xmlBufPtr buf, const xmlChar *str, int len) { - unsigned int needSize; + size_t needSize; if ((str == NULL) || (buf == NULL) || (buf->error)) return -1; @@ -888,8 +892,10 @@ xmlBufAdd(xmlBufPtr buf, const xmlChar *str, int len) { if (len < 0) return -1; if (len == 0) return 0; - needSize = buf->use + len + 2; - if (needSize > buf->size){ + if ((size_t) len >= buf->size - buf->use) { + if ((size_t) len >= SIZE_MAX - buf->use) + return(-1); + needSize = buf->use + len + 1; if (buf->alloc == XML_BUFFER_ALLOC_BOUNDED) { /* * Used to provide parsing limits @@ -1025,31 +1031,7 @@ xmlBufCat(xmlBufPtr buf, const xmlChar *str) { */ int xmlBufCCat(xmlBufPtr buf, const char *str) { - const char *cur; - - if ((buf == NULL) || (buf->error)) - return(-1); - CHECK_COMPAT(buf) - if (buf->alloc == XML_BUFFER_ALLOC_IMMUTABLE) return -1; - if (str == NULL) { -#ifdef DEBUG_BUFFER - xmlGenericError(xmlGenericErrorContext, - "xmlBufCCat: str == NULL\n"); -#endif - return -1; - } - for (cur = str;*cur != 0;cur++) { - if (buf->use + 10 >= buf->size) { - if (!xmlBufResize(buf, buf->use+10)){ - xmlBufMemoryError(buf, "growing buffer"); - return XML_ERR_NO_MEMORY; - } - } - buf->content[buf->use++] = *cur; - } - buf->content[buf->use] = 0; - UPDATE_COMPAT(buf) - return 0; + return xmlBufCat(buf, (const xmlChar *) str); } /** diff --git a/tree.c b/tree.c index 9d94aa42..86afb7d6 100644 --- a/tree.c +++ b/tree.c @@ -7104,6 +7104,8 @@ xmlBufferPtr xmlBufferCreateSize(size_t size) { xmlBufferPtr ret; + if (size >= UINT_MAX) + return(NULL); ret = (xmlBufferPtr) xmlMalloc(sizeof(xmlBuffer)); if (ret == NULL) { xmlTreeErrMemory("creating buffer"); @@ -7111,7 +7113,7 @@ xmlBufferCreateSize(size_t size) { } ret->use = 0; ret->alloc = xmlBufferAllocScheme; - ret->size = (size ? size+2 : 0); /* +1 for ending null */ + ret->size = (size ? size + 1 : 0); /* +1 for ending null */ if (ret->size){ ret->content = (xmlChar *) xmlMallocAtomic(ret->size * sizeof(xmlChar)); if (ret->content == NULL) { @@ -7171,6 +7173,8 @@ xmlBufferCreateStatic(void *mem, size_t size) { if ((mem == NULL) || (size == 0)) return(NULL); + if (size > UINT_MAX) + return(NULL); ret = (xmlBufferPtr) xmlMalloc(sizeof(xmlBuffer)); if (ret == NULL) { @@ -7318,28 +7322,23 @@ xmlBufferShrink(xmlBufferPtr buf, unsigned int len) { */ int xmlBufferGrow(xmlBufferPtr buf, unsigned int len) { - int size; + unsigned int size; xmlChar *newbuf; if (buf == NULL) return(-1); if (buf->alloc == XML_BUFFER_ALLOC_IMMUTABLE) return(0); - if (len + buf->use < buf->size) return(0); + if (len < buf->size - buf->use) + return(0); + if (len > UINT_MAX - buf->use) + return(-1); - /* - * Windows has a BIG problem on realloc timing, so we try to double - * the buffer size (if that's enough) (bug 146697) - * Apparently BSD too, and it's probably best for linux too - * On an embedded system this may be something to change - */ -#if 1 - if (buf->size > len) - size = buf->size * 2; - else - size = buf->use + len + 100; -#else - size = buf->use + len + 100; -#endif + if (buf->size > (size_t) len) { + size = buf->size > UINT_MAX / 2 ? UINT_MAX : buf->size * 2; + } else { + size = buf->use + len; + size = size > UINT_MAX - 100 ? UINT_MAX : size + 100; + } if ((buf->alloc == XML_BUFFER_ALLOC_IO) && (buf->contentIO != NULL)) { size_t start_buf = buf->content - buf->contentIO; @@ -7466,7 +7465,10 @@ xmlBufferResize(xmlBufferPtr buf, unsigned int size) case XML_BUFFER_ALLOC_IO: case XML_BUFFER_ALLOC_DOUBLEIT: /*take care of empty case*/ - newSize = (buf->size ? buf->size : size + 10); + if (buf->size == 0) + newSize = (size > UINT_MAX - 10 ? UINT_MAX : size + 10); + else + newSize = buf->size; while (size > newSize) { if (newSize > UINT_MAX / 2) { xmlTreeErrMemory("growing buffer"); @@ -7476,7 +7478,7 @@ xmlBufferResize(xmlBufferPtr buf, unsigned int size) } break; case XML_BUFFER_ALLOC_EXACT: - newSize = size+10; + newSize = (size > UINT_MAX - 10 ? UINT_MAX : size + 10);; break; case XML_BUFFER_ALLOC_HYBRID: if (buf->use < BASE_BUFFER_SIZE) @@ -7494,7 +7496,7 @@ xmlBufferResize(xmlBufferPtr buf, unsigned int size) break; default: - newSize = size+10; + newSize = (size > UINT_MAX - 10 ? UINT_MAX : size + 10);; break; } @@ -7580,8 +7582,10 @@ xmlBufferAdd(xmlBufferPtr buf, const xmlChar *str, int len) { if (len < 0) return -1; if (len == 0) return 0; - needSize = buf->use + len + 2; - if (needSize > buf->size){ + if ((unsigned) len >= buf->size - buf->use) { + if ((unsigned) len >= UINT_MAX - buf->use) + return XML_ERR_NO_MEMORY; + needSize = buf->use + len + 1; if (!xmlBufferResize(buf, needSize)){ xmlTreeErrMemory("growing buffer"); return XML_ERR_NO_MEMORY; @@ -7694,29 +7698,7 @@ xmlBufferCat(xmlBufferPtr buf, const xmlChar *str) { */ int xmlBufferCCat(xmlBufferPtr buf, const char *str) { - const char *cur; - - if (buf == NULL) - return(-1); - if (buf->alloc == XML_BUFFER_ALLOC_IMMUTABLE) return -1; - if (str == NULL) { -#ifdef DEBUG_BUFFER - xmlGenericError(xmlGenericErrorContext, - "xmlBufferCCat: str == NULL\n"); -#endif - return -1; - } - for (cur = str;*cur != 0;cur++) { - if (buf->use + 10 >= buf->size) { - if (!xmlBufferResize(buf, buf->use+10)){ - xmlTreeErrMemory("growing buffer"); - return XML_ERR_NO_MEMORY; - } - } - buf->content[buf->use++] = *cur; - } - buf->content[buf->use] = 0; - return 0; + return xmlBufferCat(buf, (const xmlChar *) str); } /** -- GitLab