From a1bf35a35c80db8493b94ea5c203a390e9479a15 Mon Sep 17 00:00:00 2001 From: Demi Obenour Date: Sun, 23 Jul 2017 14:49:07 -0400 Subject: [PATCH] Don't crash when memory is exhausted Previously, returning NULL from gumbo_parser_allocate was usually unchecked. Instead, bail out with longjmp() and return NULL from gumbo_parse_with_options(). --- src/attribute.c | 2 ++ src/error.c | 2 ++ src/parser.c | 32 ++++++++++++++++++++++++++++++++ src/parser.h | 9 +++++++++ src/util.c | 16 +++++++++++++--- tests/parser.cc | 26 ++++++++++++++++++++++++++ 6 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/attribute.c b/src/attribute.c index 234927a5..d167a16c 100644 --- a/src/attribute.c +++ b/src/attribute.c @@ -38,6 +38,8 @@ GumboAttribute* gumbo_get_attribute( void gumbo_destroy_attribute( struct GumboInternalParser* parser, GumboAttribute* attribute) { + if (NULL == attribute) + return; gumbo_parser_deallocate(parser, (void*) attribute->name); gumbo_parser_deallocate(parser, (void*) attribute->value); gumbo_parser_deallocate(parser, (void*) attribute); diff --git a/src/error.c b/src/error.c index 6b184ed4..dda601e3 100644 --- a/src/error.c +++ b/src/error.c @@ -258,6 +258,8 @@ void gumbo_print_caret_diagnostic( } void gumbo_error_destroy(GumboParser* parser, GumboError* error) { + if (NULL == error) + return; if (error->type == GUMBO_ERR_PARSER || error->type == GUMBO_ERR_UNACKNOWLEDGED_SELF_CLOSING_TAG) { gumbo_vector_destroy(parser, &error->v.parser.tag_stack); diff --git a/src/parser.c b/src/parser.c index dc692b3e..c6d4eab7 100644 --- a/src/parser.c +++ b/src/parser.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "attribute.h" #include "error.h" @@ -39,6 +40,12 @@ #define TERMINATOR \ { "", 0 } +#ifndef _WIN32 +#define SETJMP(x) (sigsetjmp(x, 0)) +#else +#define SETJMP(x) (setjmp(x)) +#endif + typedef char gumbo_tagset[GUMBO_TAG_LAST]; #define TAG(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_HTML) #define TAG_SVG(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_SVG) @@ -398,6 +405,13 @@ typedef struct GumboInternalParserState { // flag appropriately. bool _closed_body_tag; bool _closed_html_tag; + + // Jump buf to jump to on memory exhaustion. +#ifdef _WIN32 + jmp_buf oom_buf; +#else + sigjmp_buf oom_buf; +#endif } GumboParserState; static bool token_has_attribute(const GumboToken* token, const char* name) { @@ -476,6 +490,7 @@ static void output_init(GumboParser* parser) { static void parser_state_init(GumboParser* parser) { GumboParserState* parser_state = gumbo_parser_allocate(parser, sizeof(GumboParserState)); + if (NULL == parser_state) return; parser_state->_insertion_mode = GUMBO_INSERTION_MODE_INITIAL; parser_state->_reprocess_current_token = false; parser_state->_frameset_ok = true; @@ -2350,6 +2365,8 @@ static bool handle_after_head(GumboParser* parser, GumboToken* token) { } static void destroy_node(GumboParser* parser, GumboNode* node) { + if (NULL == node) + return; switch (node->type) { case GUMBO_NODE_DOCUMENT: { GumboDocument* doc = &node->v.document; @@ -4075,11 +4092,24 @@ GumboOutput* gumbo_parse(const char* buffer) { GumboOutput* gumbo_parse_with_options( const GumboOptions* options, const char* buffer, size_t length) { GumboParser parser; + memset(&parser, 0, sizeof parser); parser._options = options; + + if (SETJMP(parser._oom_buf)) { + if (parser._parser_state) + parser_state_destroy(&parser); + if (parser._tokenizer_state) + gumbo_tokenizer_state_destroy(&parser); + return NULL; + } + output_init(&parser); gumbo_tokenizer_state_init(&parser, buffer, length); parser_state_init(&parser); + if (NULL == parser._parser_state) + return NULL; + if (options->fragment_context != GUMBO_TAG_LAST) { fragment_parser_init( &parser, options->fragment_context, options->fragment_namespace); @@ -4179,6 +4209,8 @@ void gumbo_destroy_node(GumboOptions* options, GumboNode* node) { } void gumbo_destroy_output(const GumboOptions* options, GumboOutput* output) { + if (NULL == output) + return; // Need a dummy GumboParser because the allocator comes along with the // options object. GumboParser parser; diff --git a/src/parser.h b/src/parser.h index 95019e3e..c07c6006 100644 --- a/src/parser.h +++ b/src/parser.h @@ -20,6 +20,8 @@ #ifndef GUMBO_PARSER_H_ #define GUMBO_PARSER_H_ +#include + #ifdef __cplusplus extern "C" { #endif @@ -48,6 +50,13 @@ typedef struct GumboInternalParser { // The internal parser state. Initialized on parse start and destroyed on // parse end; end-users will never see a non-garbage value in this pointer. struct GumboInternalParserState* _parser_state; + + // jmp_buf to jump to on out of memory +#ifdef _WIN32 + jmp_buf _oom_buf; +#else + sigjmp_buf _oom_buf; +#endif } GumboParser; #ifdef __cplusplus diff --git a/src/util.c b/src/util.c index 5a24c115..e4bbc9bd 100644 --- a/src/util.c +++ b/src/util.c @@ -17,11 +17,12 @@ #include "util.h" #include +#include +#include +#include #include #include #include -#include -#include #include "gumbo.h" #include "parser.h" @@ -32,7 +33,16 @@ const GumboSourcePosition kGumboEmptySourcePosition = {0, 0, 0}; void* gumbo_parser_allocate(GumboParser* parser, size_t num_bytes) { - return parser->_options->allocator(parser->_options->userdata, num_bytes); + void *v = parser->_options->allocator(parser->_options->userdata, num_bytes); + if (NULL == v) { +#ifndef _WIN32 + siglongjmp(parser->_oom_buf, 1); +#else + longjmp(parser->_oom_buf, 1); +#endif + } else { + return memset(v, '\0', num_bytes); + } } void gumbo_parser_deallocate(GumboParser* parser, void* ptr) { diff --git a/tests/parser.cc b/tests/parser.cc index 4307a83d..239d2965 100644 --- a/tests/parser.cc +++ b/tests/parser.cc @@ -1954,6 +1954,32 @@ TEST_F(GumboParserTest, FragmentWithNamespace) { EXPECT_EQ(0, GetChildCount(div)); } +TEST_F(GumboParserTest, OutOfMemory) { + GumboOptions options; + memcpy((void*)&options, (void*)&kGumboDefaultOptions, sizeof options); + struct Count { + size_t count = 0; + size_t count_max = 0; + } count; + options.userdata = (void*)&count; + options.allocator = [](void *userdata, size_t size) { + auto count = (Count*)userdata; + if (count->count) { + count->count--; + return malloc(size); + } else { + count->count = ++count->count_max; + return (void*)nullptr; + } + }; + const char buf[] = "dummysome text"; + GumboOutput *output; + do { + output = gumbo_parse_with_options(&options, buf, sizeof buf - 1); + } while (output == nullptr); + gumbo_destroy_output(&options, output); +} + TEST_F(GumboParserTest, FragmentWithTwoNodes) { ParseFragment("

Hi


", GUMBO_TAG_BODY, GUMBO_NAMESPACE_HTML);