demangle/gcc2: Do not trust inputs.

We do not know anything about the symbols we are being asked to demangle;
it is entirely possible they are malformed, or that we parse them incorrectly,
which previously led to buffer overflows. E.g. the "2","8" in "SetTo__Q28_GLOBAL_"
is presently incorrectly parsed as a length, leading to an access 21 bytes past
the end of the string.

This caused a page fault under the guarded heap, a fact I had the misfortune
to discover when trying to attach Debugger to a guarded-heap'd application
which somehow ran the demangler under the guarded heap also, and that symbol
above was in runtime_loader, so it crashed while loading its symbols.

So now we do what the GCC3+ demangler does here, and keep track of the input
buffer through the use of a state class, which will prevent us from incrementing
past the buffer's end.

I've tested this patch using the new haikuc++filt utility against libtracker
(indeed, it took multiple rounds of testing to get the diff to be 0 bytes)
and it seems to work exactly as before, though now without out-of-bounds
accesses.

As this demangler is also used in the kernel, it's possible that some
triple-faults on x86_gcc2[h] are caused by this bug (although that would
be rare; one of the incorrectly-parsed symbols would have to be in the
stack trace, and then it would have to read past the end of the buffer
containing the symbol.)

Change-Id: I343991cebd7d2887812c8c6b3dc2e0df2fcd79fa
Reviewed-on: https://review.haiku-os.org/579
Reviewed-by: waddlesplash <waddlesplash@gmail.com>
This commit is contained in:
Augustin Cavalier 2018-09-17 01:15:53 -04:00 committed by waddlesplash
parent adad95fa46
commit de777b79eb

View File

@ -19,12 +19,71 @@
//#define TRACE_GCC2_DEMANGLER
#ifdef TRACE_GCC2_DEMANGLER
# define TRACE(x...) kprintf(x)
# include <stdio.h>
# define TRACE(x...) printf(x)
#else
# define TRACE(x...) ;
#endif
class Input {
public:
Input(const char* string, size_t length)
:
fString(string),
fLength(length)
{
}
const char* String() const
{
return fString;
}
int CharsRemaining() const
{
return fLength;
}
void Skip(size_t count)
{
if (count > fLength) {
TRACE("Input::Skip(): fOffset > fLength\n");
return;
}
fString += count;
fLength -= count;
}
Input& operator++()
{
Skip(1);
return *this;
}
Input operator++(int)
{
Input result(*this);
++(*this);
return result;
}
char operator[](size_t index) const
{
if (index >= fLength) {
TRACE("Input::operator[](): fOffset + index >= fLength\n");
return '\0';
}
return fString[index];
}
private:
const char* fString;
size_t fLength;
};
enum {
TYPE_FUNCTION,
TYPE_METHOD,
@ -32,29 +91,29 @@ enum {
static void
ignore_qualifiers(const char** _arg)
ignore_qualifiers(Input& _arg)
{
while (isupper(**_arg)) {
if (**_arg == 'Q') {
while (isupper(_arg[0])) {
if (_arg[0] == 'Q') {
// argument has namespaces
break;
}
if (**_arg == 'F') {
if (_arg[0] == 'F') {
// skip function declaration
while (**_arg && **_arg != '_')
(*_arg)++;
while (_arg.CharsRemaining() && _arg[0] != '_')
_arg++;
if (**_arg == 0)
if (!_arg.CharsRemaining())
break;
}
(*_arg)++;
_arg++;
}
}
static uint32
argument_type(const char* arg, size_t& length)
argument_type(Input arg, size_t& length)
{
length = sizeof(int);
@ -62,7 +121,7 @@ argument_type(const char* arg, size_t& length)
case 'P': // pointer
case 'R': // reference
length = sizeof(void*);
ignore_qualifiers(&arg);
ignore_qualifiers(arg);
if (arg[0] == 'c')
return B_STRING_TYPE;
if (arg[0] == 't') {
@ -126,9 +185,9 @@ argument_type(const char* arg, size_t& length)
static uint32
parse_number(const char** _arg, bool numberLeft)
parse_number(Input& _arg, bool numberLeft)
{
const char* arg = *_arg;
Input arg = _arg;
while (isdigit(arg[0]))
arg++;
@ -137,12 +196,14 @@ parse_number(const char** _arg, bool numberLeft)
if (arg[0] == '_' && (!numberLeft || isdigit(arg[1]))) {
// long value
value = strtoul(*_arg, (char**)_arg, 10);
if (**_arg == '_')
(*_arg)++;
const char* tmp_arg = _arg.String();
value = strtoul(tmp_arg, (char**)&tmp_arg, 10);
_arg.Skip(tmp_arg - _arg.String());
if (_arg[0] == '_')
_arg++;
} else {
value = **_arg - '0';
(*_arg)++;
value = _arg[0] - '0';
_arg++;
}
return value;
@ -150,12 +211,12 @@ parse_number(const char** _arg, bool numberLeft)
static uint32
parse_repeats(const char** _arg, uint32& index)
parse_repeats(Input& _arg, uint32& index)
{
if (**_arg != 'N')
if (_arg[0] != 'N')
return 0;
(*_arg)++;
_arg++;
uint32 count = parse_number(_arg, true);
index = parse_number(_arg, false);
@ -165,10 +226,10 @@ parse_repeats(const char** _arg, uint32& index)
static void
skip_numbers(const char** _arg, int32 count)
skip_numbers(Input& _arg, int32 count)
{
// skip leading character
(*_arg)++;
_arg++;
while (count-- > 0) {
parse_number(_arg, count != 0);
@ -177,67 +238,68 @@ skip_numbers(const char** _arg, int32 count)
static uint32
count_namespaces(const char** _mangled)
count_namespaces(Input& _mangled)
{
const char* mangled = *_mangled;
Input mangled = _mangled;
int32 namespaces = 0;
if (mangled[0] == 'Q') {
// more than one namespace
if (mangled[1] == '_') {
// more than 9 namespaces
namespaces = strtoul(mangled + 2, (char**)&mangled, 10);
const char* mangled_str = mangled.String();
namespaces = strtoul(mangled_str + 2, (char**)&mangled_str, 10);
mangled.Skip(mangled_str - mangled.String());
if (mangled[0] != '_')
namespaces = 0;
mangled++;
} else {
namespaces = mangled[1] - '0';
mangled += 2;
mangled.Skip(2);
}
} else if (isdigit(mangled[0]))
namespaces = 1;
*_mangled = mangled;
_mangled = mangled;
return namespaces;
}
static void
skip_namespaces(const char** _mangled)
skip_namespaces(Input& mangled)
{
const char* mangled = *_mangled;
int32 namespaces = count_namespaces(&mangled);
int32 namespaces = count_namespaces(mangled);
while (namespaces-- > 0) {
if (!isdigit(mangled[0]))
break;
mangled += strtoul(mangled, (char**)&mangled, 10);
const char* mangled_str = mangled.String();
mangled_str += strtoul(mangled_str, (char**)&mangled_str, 10);
mangled.Skip(mangled_str - mangled.String());
}
*_mangled = mangled;
}
static bool
has_named_argument(const char** _arg)
has_named_argument(Input& arg)
{
ignore_qualifiers(_arg);
ignore_qualifiers(arg);
// See if it's a built-in type
return **_arg == 'Q' || isdigit(**_arg);
return arg[0] == 'Q' || isdigit(arg[0]);
}
static uint32
argument_length(const char** _arg)
argument_length(Input& _arg)
{
if (**_arg == 'N') {
if (_arg[0] == 'N') {
// skip repeats
skip_numbers(_arg, 2);
return 0;
} else if (**_arg == 'T') {
} else if (_arg[0] == 'T') {
// skip reference
skip_numbers(_arg, 1);
return 0;
@ -245,59 +307,54 @@ argument_length(const char** _arg)
ignore_qualifiers(_arg);
if (!**_arg)
if (!_arg.CharsRemaining())
return 0;
// See if it's a built-in type
if (**_arg != 'Q' && !isdigit(**_arg))
if (_arg[0] != 'Q' && !isdigit(_arg[0]))
return 1;
const char* mangled = *_arg;
skip_namespaces(&mangled);
Input mangled = _arg;
skip_namespaces(mangled);
return mangled - *_arg;
return mangled.String() - _arg.String();
}
static const char*
next_argument(const char* arg)
static Input
next_argument(Input arg)
{
if (arg == NULL || !arg[0])
return NULL;
if (!arg.CharsRemaining() || !arg[0])
return arg;
uint32 length = argument_length(&arg);
arg += length;
if (!arg[0])
return NULL;
uint32 length = argument_length(arg);
arg.Skip(length);
return arg;
}
static const char*
first_argument(const char* mangled)
static Input
first_argument(Input mangled)
{
skip_namespaces(&mangled);
skip_namespaces(mangled);
return mangled;
}
static const char*
mangled_start(const char* name, size_t* _symbolLength, int32* _symbolType)
static bool
mangled_start(Input& name, size_t* _symbolLength, int32* _symbolType)
{
if (name == NULL)
return NULL;
// search '__' starting from the end, don't accept them at the start
size_t pos = strlen(name) - 1;
const char* mangled = NULL;
size_t pos = name.CharsRemaining() - 1;
bool foundStart = false;
while (pos > 1) {
if (name[pos] == '_') {
if (name[pos - 1] == '_') {
mangled = name + pos + 1;
foundStart = true;
name.Skip(pos + 1);
break;
} else
pos--;
@ -305,26 +362,27 @@ mangled_start(const char* name, size_t* _symbolLength, int32* _symbolType)
pos--;
}
if (mangled == NULL)
return NULL;
if (!foundStart)
return false;
if (mangled[0] == 'H') {
if (name[0] == 'H') {
// TODO: we don't support templates yet
return NULL;
return false;
}
if (_symbolLength != NULL)
*_symbolLength = pos - 1;
if (mangled[0] == 'F') {
if (name[0] == 'F') {
if (_symbolType != NULL)
*_symbolType = TYPE_FUNCTION;
return mangled + 1;
name.Skip(1);
return true;
}
if (_symbolType != NULL)
*_symbolType = TYPE_METHOD;
return mangled;
return true;
}
@ -332,11 +390,11 @@ static status_t
get_next_argument_internal(uint32* _cookie, const char* symbol, char* name,
size_t nameSize, int32* _type, size_t* _argumentLength, bool repeating)
{
const char* mangled = mangled_start(symbol, NULL, NULL);
if (mangled == NULL)
Input mangled(symbol, strlen(symbol));
if (!mangled_start(mangled, NULL, NULL))
return B_BAD_VALUE;
const char* arg = first_argument(mangled);
Input arg = first_argument(mangled);
// (void) is not an argument
if (arg[0] == 'v')
@ -348,10 +406,10 @@ get_next_argument_internal(uint32* _cookie, const char* symbol, char* name,
for (uint32 i = 0; i < current; i++) {
arg = next_argument(arg);
if (arg != NULL && arg[0] == 'N') {
if (arg.CharsRemaining() && arg[0] == 'N') {
// repeat argument 'count' times
uint32 index;
uint32 count = parse_repeats(&arg, index);
uint32 count = parse_repeats(arg, index);
if (current <= i + count) {
if (repeating)
return B_LINK_LIMIT;
@ -368,10 +426,10 @@ get_next_argument_internal(uint32* _cookie, const char* symbol, char* name,
}
}
if (arg == NULL)
if (arg[0] == '\0' || !arg.CharsRemaining())
return B_ENTRY_NOT_FOUND;
TRACE("\n\targ %ld: %s\n", current, arg);
TRACE("\n\targ %ld: %s\n", current, arg.String());
if (arg[0] == 'T') {
// duplicate argument
@ -379,7 +437,7 @@ get_next_argument_internal(uint32* _cookie, const char* symbol, char* name,
return B_LINK_LIMIT;
arg++;
uint32 index = parse_number(&arg, false);
uint32 index = parse_number(arg, false);
status_t status = get_next_argument_internal(&index, symbol, name,
nameSize, _type, _argumentLength, true);
if (status == B_OK)
@ -400,11 +458,11 @@ get_next_argument_internal(uint32* _cookie, const char* symbol, char* name,
*_argumentLength = argumentLength;
name[0] = '\0';
if (!has_named_argument(&arg))
if (!has_named_argument(arg))
return B_OK;
const char* namespaceStart = arg;
int32 namespaces = count_namespaces(&namespaceStart);
Input namespaceStart = arg;
int32 namespaces = count_namespaces(namespaceStart);
while (namespaces-- > 0) {
if (namespaceStart[0] == 't') {
@ -414,12 +472,15 @@ get_next_argument_internal(uint32* _cookie, const char* symbol, char* name,
if (!isdigit(namespaceStart[0]))
break;
uint32 length = strtoul(namespaceStart, (char**)&namespaceStart, 10);
const char* ns_str = namespaceStart.String();
uint32 length = strtoul(ns_str, (char**)&ns_str, 10);
namespaceStart.Skip(ns_str - namespaceStart.String());
uint32 max = strlen(name) + length + 1;
strlcat(name, namespaceStart, min_c(max, nameSize));
strlcat(name, namespaceStart.String(), min_c(max, nameSize));
if (namespaces > 0)
strlcat(name, "::", nameSize);
namespaceStart += length;
namespaceStart.Skip(length);
}
return B_OK;
@ -433,10 +494,13 @@ const char*
demangle_symbol_gcc2(const char* name, char* buffer, size_t bufferSize,
bool* _isObjectMethod)
{
if (name == NULL)
return NULL;
size_t nameLength;
int32 type;
const char* mangled = mangled_start(name, &nameLength, &type);
if (mangled == NULL)
Input mangled(name, strlen(name));
if (!mangled_start(mangled, &nameLength, &type))
return NULL;
if (mangled[0] == 'C') {
@ -450,8 +514,8 @@ demangle_symbol_gcc2(const char* name, char* buffer, size_t bufferSize,
*_isObjectMethod = type == TYPE_METHOD;
}
const char* namespaceStart = mangled;
int32 namespaces = count_namespaces(&namespaceStart);
Input namespaceStart = mangled;
int32 namespaces = count_namespaces(namespaceStart);
buffer[0] = '\0';
@ -463,11 +527,14 @@ demangle_symbol_gcc2(const char* name, char* buffer, size_t bufferSize,
if (!isdigit(namespaceStart[0]))
break;
uint32 length = strtoul(namespaceStart, (char**)&namespaceStart, 10);
const char* ns_str = namespaceStart.String();
uint32 length = strtoul(ns_str, (char**)&ns_str, 10);
namespaceStart.Skip(ns_str - namespaceStart.String());
uint32 max = strlen(buffer) + length + 1;
strlcat(buffer, namespaceStart, min_c(max, bufferSize));
strlcat(buffer, namespaceStart.String(), min_c(max, bufferSize));
strlcat(buffer, "::", bufferSize);
namespaceStart += length;
namespaceStart.Skip(length);
}
size_t max = strlen(buffer) + nameLength + 1;