# HG changeset patch # User David Kilzer <ddkilzer@apple.com> # Date 1652478213 25200 # Fri May 13 14:43:33 2022 -0700 # Node ID 37aeffd2f03981fb3090960ba53bb5f5c962970d # Parent 7c13fc7896d20df4f0f70305ec48766090c8389f Fix more overflow checks, off-by-ones and missing NUL terminators in xmlBuf and xmlBuffer In broad strokes, this does the following: - Do not include the NUL terminator byte for lengths returned from functions. This lets functions be more defensive. - Set error messages when returning early due to out-of-memory or buffer-too-large errors. - Set NUL terminator consistently on buffer boundaries before returning. - Add a few more integer overflow checks. * buf.c: (xmlBufGrowInternal): - Do not include NUL terminator byte when returning length. - Always set NUL terminator at the end of the new buffer length before returning. - Call xmlBufMemoryError() when the buffer size would overflow. - Account for NUL terminator byte when using XML_MAX_TEXT_LENGTH. - Always set NUL terminator at the end of the current buffer after resizing the buffer. (xmlBufAddLen): - Return an error if the buffer does not have free space for the NUL terminator byte. (xmlBufAvail): - Do not include the NUL terminator byte in the length returned. (See changes to encoding.c and xmlIO.c.) (xmlBufResize): - Move setting of NUL terminator to common code. More than one path through the function failed to set it. (xmlBufAdd): - Call xmlBufMemoryError() when the buffer size would overflow. * encoding.c: (xmlCharEncFirstLineInput): (xmlCharEncInput): (xmlCharEncOutput): - No longer need to subtract one from the return value of xmlBufAvail() since the function does this now. * testchar.c: (testCharRanges): - Pass the string length without the NUL terminator. * tree.c: (xmlBufferGrow): - Do not include NUL terminator byte when returning length. - Always set NUL terminator at the end of the new buffer length before returning. - Call xmlTreeErrMemory() when the buffer size would overflow. - Always set NUL terminator at the end of the current buffer after resizing the buffer. (xmlBufferDump): - Change type of the return variable to match fwrite(). - Clamp return value to INT_MAX to prevent overflow. (xmlBufferResize): - Update error message in xmlTreeErrMemory() to be consistent with other similar messages. - Move setting of NUL terminator to common code. More than one path through the function failed to set it. (xmlBufferAdd): - Call xmlTreeErrMemory() when the buffer size would overflow. (xmlBufferAddHead): - Set NUL terminator before returning early when shifting contents. - Add overflow checks similar to those in xmlBufferAdd(). * xmlIO.c: (xmlOutputBufferWriteEscape): - No longer need to subtract one from the return value of xmlBufAvail() since the function does this now. diff --git a/buf.c b/buf.c --- a/buf.c +++ b/buf.c @@ -435,10 +435,14 @@ CHECK_COMPAT(buf) if (buf->alloc == XML_BUFFER_ALLOC_IMMUTABLE) return(0); - if (len < buf->size - buf->use) - return(buf->size - buf->use); - if (len > SIZE_MAX - buf->use) + if (len < buf->size - buf->use) { + buf->content[buf->use + len] = 0; + return(buf->size - buf->use - 1); + } + if (len > SIZE_MAX - buf->use - 1) { + xmlBufMemoryError(buf, "growing buffer past SIZE_MAX"); return(0); + } if (buf->size > (size_t) len) { size = buf->size > SIZE_MAX / 2 ? SIZE_MAX : buf->size * 2; @@ -451,7 +455,7 @@ /* * Used to provide parsing limits */ - if ((buf->use + len >= XML_MAX_TEXT_LENGTH) || + if ((buf->use + len + 1 >= XML_MAX_TEXT_LENGTH) || (buf->size >= XML_MAX_TEXT_LENGTH)) { xmlBufMemoryError(buf, "buffer error: text too long\n"); return(0); @@ -478,8 +482,10 @@ buf->content = newbuf; } buf->size = size; + buf->content[buf->use] = 0; + buf->content[buf->use + len] = 0; UPDATE_COMPAT(buf) - return(buf->size - buf->use); + return(buf->size - buf->use - 1); } /** @@ -591,14 +597,11 @@ if ((buf == NULL) || (buf->error)) return(-1); CHECK_COMPAT(buf) - if (len > (buf->size - buf->use)) + if (len >= (buf->size - buf->use)) return(-1); buf->use += len; + buf->content[buf->use] = 0; UPDATE_COMPAT(buf) - if (buf->size > buf->use) - buf->content[buf->use] = 0; - else - return(-1); return(0); } @@ -658,7 +661,7 @@ return 0; CHECK_COMPAT(buf) - return(buf->size - buf->use); + return((buf->size > buf->use) ? (buf->size - 1) - buf->use : 0); } /** @@ -762,7 +765,6 @@ /* move data back to start */ memmove(buf->contentIO, buf->content, buf->use); buf->content = buf->contentIO; - buf->content[buf->use] = 0; buf->size += start_buf; } else { rebuf = (xmlChar *) xmlRealloc(buf->contentIO, start_buf + newSize); @@ -788,7 +790,6 @@ if (rebuf != NULL) { memcpy(rebuf, buf->content, buf->use); xmlFree(buf->content); - rebuf[buf->use] = 0; } } if (rebuf == NULL) { @@ -798,6 +799,7 @@ buf->content = rebuf; } buf->size = newSize; + buf->content[buf->use] = 0; UPDATE_COMPAT(buf) return 1; @@ -839,9 +841,12 @@ if (len < 0) return -1; if (len == 0) return 0; + /* Note that both buf->size and buf->use can be zero here. */ if ((size_t) len >= buf->size - buf->use) { - if ((size_t) len >= SIZE_MAX - buf->use) + if ((size_t) len >= SIZE_MAX - buf->use) { + xmlBufMemoryError(buf, "growing buffer past SIZE_MAX"); return(-1); + } needSize = buf->use + len + 1; if (buf->alloc == XML_BUFFER_ALLOC_BOUNDED) { /* diff --git a/encoding.c b/encoding.c --- a/encoding.c +++ b/encoding.c @@ -2197,7 +2197,7 @@ toconv = xmlBufUse(in); if (toconv == 0) return (0); - written = xmlBufAvail(out) - 1; /* count '\0' */ + written = xmlBufAvail(out); /* * echo '<?xml version="1.0" encoding="UCS4"?>' | wc -c => 38 * 45 chars should be sufficient to reach the end of the encoding @@ -2215,7 +2215,7 @@ } if (toconv * 2 >= written) { xmlBufGrow(out, toconv * 2); - written = xmlBufAvail(out) - 1; + written = xmlBufAvail(out); } if (written > 360) written = 360; @@ -2307,13 +2307,9 @@ if ((toconv > 64 * 1024) && (flush == 0)) toconv = 64 * 1024; written = xmlBufAvail(out); - if (written > 0) - written--; /* count '\0' */ if (toconv * 2 >= written) { xmlBufGrow(out, toconv * 2); written = xmlBufAvail(out); - if (written > 0) - written--; /* count '\0' */ } if ((written > 128 * 1024) && (flush == 0)) written = 128 * 1024; @@ -2495,8 +2491,6 @@ retry: written = xmlBufAvail(out); - if (written > 0) - written--; /* count '\0' */ /* * First specific handling of the initialization call @@ -2525,7 +2519,7 @@ toconv = 64 * 1024; if (toconv * 4 >= written) { xmlBufGrow(out, toconv * 4); - written = xmlBufAvail(out) - 1; + written = xmlBufAvail(out); } if (written > 256 * 1024) written = 256 * 1024; diff --git a/testchar.c b/testchar.c --- a/testchar.c +++ b/testchar.c @@ -607,7 +607,7 @@ fprintf(stderr, "Failed to allocate parser context\n"); return(1); } - buf = xmlParserInputBufferCreateStatic(data, sizeof(data), + buf = xmlParserInputBufferCreateStatic(data, sizeof(data) - 1, XML_CHAR_ENCODING_NONE); if (buf == NULL) { fprintf(stderr, "Failed to allocate input buffer\n"); diff --git a/tree.c b/tree.c --- a/tree.c +++ b/tree.c @@ -7369,10 +7369,14 @@ if (buf == NULL) return(-1); if (buf->alloc == XML_BUFFER_ALLOC_IMMUTABLE) return(0); - if (len < buf->size - buf->use) + if (len < buf->size - buf->use) { + buf->content[buf->use + len] = 0; return(0); - if (len > UINT_MAX - buf->use) + } + if (len > UINT_MAX - buf->use - 1) { + xmlTreeErrMemory("growing buffer past UINT_MAX"); return(-1); + } if (buf->size > (size_t) len) { size = buf->size > UINT_MAX / 2 ? UINT_MAX : buf->size * 2; @@ -7400,7 +7404,9 @@ buf->content = newbuf; } buf->size = size; - return(buf->size - buf->use); + buf->content[buf->use] = 0; + buf->content[buf->use + len] = 0; + return(buf->size - buf->use - 1); } /** @@ -7413,7 +7419,7 @@ */ int xmlBufferDump(FILE *file, xmlBufferPtr buf) { - int ret; + size_t ret; if (buf == NULL) { #ifdef DEBUG_BUFFER @@ -7432,7 +7438,7 @@ if (file == NULL) file = stdout; ret = fwrite(buf->content, sizeof(xmlChar), buf->use, file); - return(ret); + return(ret > INT_MAX ? INT_MAX : (int)ret); } /** @@ -7497,7 +7503,7 @@ return 1; if (size > UINT_MAX - 10) { - xmlTreeErrMemory("growing buffer"); + xmlTreeErrMemory("growing buffer past UINT_MAX"); return 0; } @@ -7548,7 +7554,6 @@ /* move data back to start */ memmove(buf->contentIO, buf->content, buf->use); buf->content = buf->contentIO; - buf->content[buf->use] = 0; buf->size += start_buf; } else { rebuf = (xmlChar *) xmlRealloc(buf->contentIO, start_buf + newSize); @@ -7574,7 +7579,6 @@ if (rebuf != NULL) { memcpy(rebuf, buf->content, buf->use); xmlFree(buf->content); - rebuf[buf->use] = 0; } } if (rebuf == NULL) { @@ -7584,6 +7588,7 @@ buf->content = rebuf; } buf->size = newSize; + buf->content[buf->use] = 0; return 1; } @@ -7623,9 +7628,12 @@ if (len < 0) return -1; if (len == 0) return 0; + /* Note that both buf->size and buf->use can be zero here. */ if ((unsigned) len >= buf->size - buf->use) { - if ((unsigned) len >= UINT_MAX - buf->use) + if ((unsigned) len >= UINT_MAX - buf->use) { + xmlTreeErrMemory("growing buffer past UINT_MAX"); return XML_ERR_NO_MEMORY; + } needSize = buf->use + len + 1; if (!xmlBufferResize(buf, needSize)){ xmlTreeErrMemory("growing buffer"); @@ -7690,11 +7698,17 @@ memmove(&buf->content[0], str, len); buf->use += len; buf->size += len; + buf->content[buf->use] = 0; return(0); } } - needSize = buf->use + len + 2; - if (needSize > buf->size){ + /* Note that both buf->size and buf->use can be zero here. */ + if ((unsigned) len >= buf->size - buf->use) { + if ((unsigned) len >= UINT_MAX - buf->use) { + xmlTreeErrMemory("growing buffer past UINT_MAX"); + return(-1); + } + needSize = buf->use + len + 1; if (!xmlBufferResize(buf, needSize)){ xmlTreeErrMemory("growing buffer"); return XML_ERR_NO_MEMORY; diff --git a/xmlIO.c b/xmlIO.c --- a/xmlIO.c +++ b/xmlIO.c @@ -3547,7 +3547,7 @@ * how many bytes to consume and how many bytes to store. */ cons = len; - chunk = xmlBufAvail(out->buffer) - 1; + chunk = xmlBufAvail(out->buffer); /* * make sure we have enough room to save first, if this is