Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heap Double Free / Use-After-Free #144

Closed
r0t0tiller opened this issue Apr 26, 2017 · 6 comments
Closed

Heap Double Free / Use-After-Free #144

r0t0tiller opened this issue Apr 26, 2017 · 6 comments

Comments

@r0t0tiller
Copy link

Summary:

Gravity is vulnerable to a Heap-Double-Free/Use-After-Free condition. 
Execution Process:

./gravity testcase

Allocated:

Line 524 of gravity_lexer.c allocates memory for the 'lexer' pointer.

gravity_lexer_t *gravity_lexer_create (const char *source, size_t len, uint32_t fileid, bool is_static) {
	gravity_lexer_t *lexer = mem_alloc(sizeof(gravity_lexer_t));
	if (!lexer) return NULL;
	bzero(lexer, sizeof(gravity_lexer_t));
	
	lexer->is_static = is_static;
	lexer->lineno = 1;
	lexer->buffer = source;
	lexer->length = (uint32_t)len;
	lexer->fileid = fileid;
	lexer->peeking = false;
	return lexer;
}
Freed:

Line 763 of gravity_parser.c creates a pointer called 'sublexer' to the gravity_lexer_create() function. 'sublexer' now points to 'lexer'.

static gnode_t *parse_analyze_literal_string (gravity_parser_t *parser, gtoken_s token, const char *s, uint32_t len) {
    
    // check for special built-in cases first
    if (token.builtin != BUILTIN_NONE) {
        if (token.builtin == BUILTIN_FILE) {
            if (parser->delegate && parser->delegate->filename_callback) {
                const char *filename = parser->delegate->filename_callback(token.fileid, parser->delegate->xdata);
                if (!filename) filename = "";
                return gnode_literal_string_expr_create(token, (char *)filename, (uint32_t)strlen(filename), false, LAST_DECLARATION());
            }
        }
        else if (token.builtin == BUILTIN_FUNC) {
            gnode_function_decl_t *node = (gnode_function_decl_t *)get_enclosing(parser, NODE_FUNCTION_DECL);
            const char *identifier = (node && node->identifier) ? (node->identifier) : "";
            return gnode_literal_string_expr_create(token, (char *)identifier, (uint32_t)strlen(identifier), false, LAST_DECLARATION());
        }
        else if (token.builtin == BUILTIN_CLASS) {
            gnode_class_decl_t *node = (gnode_class_decl_t *)get_enclosing(parser, NODE_CLASS_DECL);
            const char *identifier = (node && node->identifier) ? (node->identifier) : "";
            return gnode_literal_string_expr_create(token, (char *)identifier, (uint32_t)strlen(identifier), false, LAST_DECLARATION());
        }
    }
    
	// used in string interpolation
	gnode_r *r = NULL;
	
	// analyze s (of length len) for escaped characters or for interpolations
	char *buffer = mem_alloc(len+1);
	uint32_t length = 0;
	
	for (uint32_t i=0; i<len;) {
		int c = s[i];
		if (c == '\\') {
			// handle escape sequence here
			if (i+1 >= len) {REPORT_ERROR(token, "Unexpected EOF inside a string literal"); goto return_string;}
			switch (s[i+1]) {
				case '\'': c = '\''; ++i; break;
				case '"':  c = '"'; ++i; break;
				case '\\': c = '\\'; ++i; break;
				case 'a': c = '\a'; ++i; break;
				case 'b': c = '\b'; ++i; break;
				case 'f': c = '\f'; ++i; break;
				case 'n': c = '\n'; ++i; break;
				case 'r': c = '\r'; ++i; break;
				case 't': c = '\t'; ++i; break;
				case 'v': c = '\v'; ++i; break;
				case 'x': {
					// double hex digits sequence
					// \XFF
					if (i+1+2 >= len) {REPORT_ERROR(token, "Unexpected EOF inside a string literal"); goto return_string;}
					// setup a static buffer assuming the next two characters are hex
					char b[3] = {s[i+2], s[i+3], 0};
					// convert from base 16 to base 10 (FF is at maximum 255)
					c = (int)strtoul(b, NULL, 16);
					buffer[length] = c;
					// i+2 is until \x plus 2 hex characters
					i+=2+2; ++length;
					continue;
				}
				case 'u':  {
					// 4 digits unicode sequence
					// \uXXXX
					if (i+1+4 >= len) {REPORT_ERROR(token, "Unexpected EOF inside a string literal"); goto return_string;}
					// setup a static buffer assuming the next four characters are hex
					char b[5] = {s[i+2], s[i+3], s[i+4], s[i+5], 0};
					// convert from base 16 to base 10 (FFFF is at maximum 65535)
					uint32_t n = (uint32_t)strtoul(b, NULL, 16);
					length += utf8_encode(&buffer[length], n);
					i+=2+4;
					continue;
				}
				case 'U':  {
					// 8 digits unicode sequence
					// \uXXXXXXXX
					if (i+1+8 >= len) {REPORT_ERROR(token, "Unexpected EOF inside a string literal"); goto return_string;}
					// setup a static buffer assuming the next height characters are hex
					char b[9] = {s[i+2], s[i+3], s[i+4], s[i+5], s[i+6], s[i+7], s[i+8], s[i+9], 0};
					// convert from base 16 to base 10 (FFFF is at maximum 4294967295)
					uint32_t n = (uint32_t)strtoul(b, NULL, 16);
					length += utf8_encode(&buffer[length], n);
					i+=2+8;
					continue;
				}
				case '(': {
					// string interpolation case
					i+=2; // skip \ and (
					uint32_t j=i;
                    uint32_t nesting_level = 0;
					bool subfound = false;
					while (i<len) {
                        if (s[i] == ')') {
                            if (nesting_level == 0) subfound = true;
                            else --nesting_level;
                        }
                        else if (s[i] == '(') {
                            ++nesting_level;
                        }
						++i;
						if (subfound) break;
					}
					if (!subfound || nesting_level != 0) {
                        REPORT_ERROR(token, "Malformed interpolation string not closed by )");
                        goto return_string;
                    }
					
					uint32_t sublen = i - j;
					
					// create a new temp lexer
					gravity_lexer_t	*sublexer = gravity_lexer_create(&s[j], sublen, 0, true);
					marray_push(gravity_lexer_t*, *parser->lexer, sublexer);
					
					// parse interpolated expression
					gnode_t *subnode = parse_expression(parser);
					if (!subnode) goto return_string;
					
					// add expression to r
					if (!r) r = gnode_array_create();
					if (length) gnode_array_push(r, gnode_literal_string_expr_create(token, buffer, length, true, LAST_DECLARATION()));
					gnode_array_push(r, subnode);
					
					// free temp lexer
					marray_pop(*parser->lexer);
					gravity_lexer_free(sublexer); // Frees the pointer to lexer
					
					buffer = mem_alloc(len+1);
					length = 0;
					
					continue;
				}
				default:
					// ignore unknown sequence
					break;
			}
			
		}
		buffer[length] = c;
		++i; ++length;
	}
	
return_string:
	// append the last string if any and if interpolation mode is on
	if (r && length) gnode_array_push(r, gnode_literal_string_expr_create(token, buffer, length, true, LAST_DECLARATION()));
	
	// return a node (even in case of error) so its memory will be automatically freed
	return (r) ? gnode_string_interpolation_create(token, r, LAST_DECLARATION()) : gnode_literal_string_expr_create(token, buffer, length, true, LAST_DECLARATION());
}


Freed Again (Double Free):

Line 2476 of gravity_parser.c frees the pointer to 'lexer' again creating a Heap-Double-Free condition. This has the potential to create a Heap-Use-After-Free condition

as well. Anytime 'lexer' is used, after gravity_lexer_free(sublexer); will create a Heap-Use-After-Free conditon. In addition, another Heap-Double-Free conditon occurs in line 2564 will free 'lexer' again causing another Heap-Double-Free conditon.

static uint32_t parser_run (gravity_parser_t *parser) {
	DEBUG_PARSER("=== BEGIN PARSING ===");
	
	// register core classes as extern globals
	parser_register_core_classes(parser);
	
	nanotime_t t1 = nanotime();
	do {
		while (gravity_lexer_peek(CURRENT_LEXER)) {
			gnode_t *node = parse_statement(parser);
			if (node) gnode_array_push(parser->statements, node);
		}
		
		// since it is a stack of lexers then check if it is a real EOF
		gravity_lexer_t *lexer = CURRENT_LEXER;
		gravity_lexer_free(lexer); // Double Free
		marray_pop(*parser->lexer);
		
	} while (marray_size(*parser->lexer));
	nanotime_t t2 = nanotime();
	parser->time = millitime(t1, t2);
	
	DEBUG_PARSER("===  END PARSING  ===\n");
	
	return parser->nerrors;
}
Another Double Free: 

Line 2564 of gravity_parser.c free's the lexer pointer again.

void gravity_parser_free (gravity_parser_t *parser) {
	// free memory for stack of lexers
	if (parser->lexer) {
		size_t _len = marray_size(*parser->lexer);
		for (size_t i=0; i<_len; ++i) {
			gravity_lexer_t *lexer = (gravity_lexer_t *)marray_get(*parser->lexer, i);
			gravity_lexer_free(lexer); // Double Free
		}
		marray_destroy(*parser->lexer);
		mem_free(parser->lexer);
	}
@r0t0tiller
Copy link
Author

A example of a Heap-Use-After-Free after the 'sublexer' pointer has been freed. Line 542 of gravity_lexer.c. 'lexer' is being used to access a variable but 'lexer' has already been freed, creating a Heap Use-After-Free condition.

gtoken_t gravity_lexer_peek (gravity_lexer_t *lexer) {
	lexer->peeking = true; // lexer Heap-Use-After-Free
	gravity_lexer_t saved = *lexer;

	gtoken_t result = gravity_lexer_next(lexer);

	*lexer = saved;
	lexer->peeking = false;

	return result;
}

@r0t0tiller
Copy link
Author

Attached is a very strange testcase that triggers the Heap Use-After-Free / Double Free.
testcase.tar.gz

@marcobambini
Copy link
Owner

Thanks @tylerp96, fixed by 5b73d29.
It works like a stack of lexers, so there is no double free is the temp lexer is correctly POP from the stack.

@r0t0tiller
Copy link
Author

My testcase did trigger a Use-After-Free condition though. Were you able to confirm this?

@marcobambini
Copy link
Owner

Yes @tylerp96 I was able to reproduce and fix it.

@r0t0tiller
Copy link
Author

Okay awesome! Just making sure we were on the same page. Good deal!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants