Search code examples
c++11c++14libxml2

libxml++2.36: "double free or corruption" if a std::exception is thrown


Hy, while using the libxml++-2.36-library for my code I found out, that this library produces a "double free or corruption"-error, when exceptions of the basetype std::exception are thrown in the sax-parser-callbacks. (e.g. on_start_document, on_end_document, on_...)

But it behaves normally, this means, that the exception can be catched, if an exception of basetype xmlpp::exception is thrown.

Interestingly xmlpp::exception is based on std::exception.

To verify this, I have created a MWE:

#include <libxml++/libxml++.h>
#include <iostream>

class xml_parser : public xmlpp::SaxParser {
protected:
    virtual void on_start_document() override;
};

void xml_parser::on_start_document() {
    throw std::runtime_error("test-exception");
    //throw xmlpp::internal_error("test-exception");
}

int main(void) {
    xml_parser parser;
    try {
        parser.parse_memory(
            u8"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
    } catch (std::exception &e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }
}

It can be compiled by:

g++  main.cpp -o main `pkg-config --cflags --libs libxml++-2.6`  -Wall -pedantic -Wextra -Werror -Wcast-qual -Wcast-align -Wconversion -fdiagnostics-color=auto -g -O2 -std=c++11

If I execute the file as it is, I get the following output:

*** Error in `./main': double free or corruption (!prev): 0x0000000000b35b30 ***

If I uncomment the second line and comment the first line of the on_start_document(), I get the following output:

Caught exception: test-exception

Compiler: g++ 4.9.2 libxml++: 2.6 - 2.36

Is there a way, to get std::exception based exceptions working, without creating special xmlpp::exception-Based Exceptions?


Solution

  • What's happening is that an _xmlSAXHandler object is being freed twice, once by delete and another by xmlFree(). This can be seen by setting a break point on the appropriate free() standard libc call. For example, on an Ubuntu 'trusty' machine, I set a breakpoint on _int_free and saw that the same pointer was being freed twice:

    Breakpoint 2, _int_free (av=0x7ffff741f760 , p=0x610b80, 
        have_lock=0) at malloc.c:3814
    3814    in malloc.c
    (gdb) bt
    #0  _int_free (av=0x7ffff741f760 , p=0x610b80, have_lock=0)
        at malloc.c:3814
    #1  0x00007ffff6d32a59 in xmlFreeParserCtxt ()
       from /usr/lib/x86_64-linux-gnu/libxml2.so.2
    #2  0x00007ffff7bc6be2 in xmlpp::Parser::release_underlying (
        this=this@entry=0x7fffffffdd30) at libxml++/parsers/parser.cc:162
    #3  0x00007ffff7bcc665 in xmlpp::SaxParser::release_underlying (
        this=this@entry=0x7fffffffdd30) at libxml++/parsers/saxparser.cc:329
    #4  0x00007ffff7bcc68c in xmlpp::SaxParser::~SaxParser (this=0x7fffffffdd30, 
        __in_chrg=) at libxml++/parsers/saxparser.cc:83
    #5  0x0000000000401d80 in ~xml_parser (this=0x7fffffffdd30, 
        __in_chrg=) at so31969961_libxmlpp_double_free.cpp:7
    #6  main () at so31969961_libxmlpp_double_free.cpp:24
    
    ...
    
    Breakpoint 2, _int_free (av=0x7ffff741f760 , p=0x610b80, 
        have_lock=0) at malloc.c:3814
    3814    in malloc.c
    (gdb) bt
    #0  _int_free (av=0x7ffff741f760 , p=0x610b80, have_lock=0)
        at malloc.c:3814
    #1  0x00007ffff7bcc69e in ~auto_ptr (this=0x7fffffffdd60, 
        __in_chrg=) at /usr/include/c++/4.8/backward/auto_ptr.h:170
    #2  xmlpp::SaxParser::~SaxParser (this=0x7fffffffdd30, 
        __in_chrg=) at libxml++/parsers/saxparser.cc:81
    #3  0x0000000000401d80 in ~xml_parser (this=0x7fffffffdd30, 
        __in_chrg=) at so31969961_libxmlpp_double_free.cpp:7
    #4  main () at so31969961_libxmlpp_double_free.cpp:24
    

    In this case, 0x610b80 corresponds to the _xmlSAXHandler object held by the SaxParser in its sax_handler_ auto_ptr member. It is first freed by libxml2's xmlFreeParserCtxt() routine. It is then deleted by the std::auto_ptr<_xmlSAXHandler> destructor.

    If you look at the source of libxml++'s SaxParser class, in saxparser.cc, you will see that there are several try..catch statements. However, only const exception& is caught. This const exception is not std::exception but rather xmlpp::exception.

    When you throw a std::runtime_error in the on_start_document() handler, it is not caught by SaxParserCallback::start_document(). Consequently, as the stack is unwound, the code in SaxParser::parse() that restores the original _xmlSAXHandler pointer in the _xmlParserCtxt is skipped.

    The takeaway from this is that you should only throw exceptions deriving from xmlpp::exception within the SaxParser handler methods.

    UPDATE: https://bugzilla.gnome.org/show_bug.cgi?id=753570

    UPDATE2: Fixed in version 2.39.2.