diff --git a/demangle.cpp b/demangle.cpp index 9d3a9aee71..fb7ffaa4da 100644 --- a/demangle.cpp +++ b/demangle.cpp @@ -1,4 +1,5 @@ #include "binaryninjaapi.h" +#include #include #include using namespace std; @@ -299,7 +300,19 @@ namespace BinaryNinja bool Demangler::IsMangledStringCallback(void* ctxt, const char* mangledName) { auto demangler = static_cast(ctxt); - return demangler->IsMangledString(mangledName); + try + { + return mangledName && demangler->IsMangledString(mangledName); + } + catch (const std::exception& e) + { + LogErrorForException(e, "Demangler name callback failed: %s", e.what()); + } + catch (...) + { + LogError("Demangler name callback failed with an unknown exception"); + } + return false; } bool Demangler::DemangleCallback(void* ctxt, const char* mangledName, const BNDemanglerConfig* config, @@ -310,12 +323,24 @@ namespace BinaryNinja if (!mangledName || !result) return false; - auto demangleResult = demangler->Demangle(mangledName, DemanglerConfig::FromAPIStruct(config)); - if (!demangleResult) - return false; + try + { + auto demangleResult = demangler->Demangle(mangledName, DemanglerConfig::FromAPIStruct(config)); + if (!demangleResult) + return false; - *result = demangleResult->ToAPIStruct(); - return true; + *result = demangleResult->ToAPIStruct(); + return true; + } + catch (const std::exception& e) + { + LogErrorForException(e, "Demangler callback failed: %s", e.what()); + } + catch (...) + { + LogError("Demangler callback failed with an unknown exception"); + } + return false; } void Demangler::FreeResultCallback(void* ctxt, BNDemanglerResult* result) diff --git a/demangler/demangled_template_simplifier.cpp b/demangler/demangled_template_simplifier.cpp index 2f2d7f13bb..aab4c1537c 100644 --- a/demangler/demangled_template_simplifier.cpp +++ b/demangler/demangled_template_simplifier.cpp @@ -2245,6 +2245,9 @@ namespace // Compatibility parser for qualified names that do not come from a demangler AST. // Native GNU3/MSVC paths use the structured DemangledTypeNode overloads. + constexpr size_t MaxCompatibilityTemplateDepth = 128; + struct CompatibilityTemplateDepthExceeded {}; + size_t FindTemplateOpen(std::string_view component) { component = TrimSpaces(component); @@ -2321,20 +2324,22 @@ namespace return out; } - DemangledTypeNode ParseCompatibilityType(std::string_view s); + DemangledTypeNode ParseCompatibilityType(std::string_view s, size_t depth); - ParamList ParseCompatibilityArgs(std::string_view s) + ParamList ParseCompatibilityArgs(std::string_view s, size_t depth) { + if (depth > MaxCompatibilityTemplateDepth) + throw CompatibilityTemplateDepthExceeded{}; ParamList args; for (std::string_view arg: SplitTopLevel(s, ',')) { - DemangledTypeNode node = ParseCompatibilityType(arg); + DemangledTypeNode node = ParseCompatibilityType(arg, depth); args.push_back({"", DemangledTypeNode::CreateShared(std::move(node))}); } return args; } - DemangledQualifiedName ParseCompatibilityName(std::string_view s) + DemangledQualifiedName ParseCompatibilityName(std::string_view s, size_t depth) { DemangledQualifiedName out; for (std::string_view part: SplitTopLevel(s, ':')) @@ -2355,20 +2360,21 @@ namespace std::string_view base = TrimSpaces(part.substr(0, open)); DemangledNamePart segment{base}; - segment.SetTemplateArguments(ParseCompatibilityArgs(part.substr(open + 1, close - open - 1)), true); + segment.SetTemplateArguments( + ParseCompatibilityArgs(part.substr(open + 1, close - open - 1), depth + 1), true); out.push_back(std::move(segment)); } return out; } - DemangledTypeNode ParseCompatibilityType(std::string_view s) + DemangledTypeNode ParseCompatibilityType(std::string_view s, size_t depth) { s = StripLeadingTypeKeyword(s); if (s.empty()) return DemangledTypeNode::NamedType(UnknownNamedTypeClass, StringList{""}); if (s.find("::") == std::string_view::npos && FindTemplateOpen(s) == std::string_view::npos) return DemangledTypeNode::NamedType(UnknownNamedTypeClass, StringList{string(s)}); - return DemangledTypeNode::NamedType(UnknownNamedTypeClass, ParseCompatibilityName(s)); + return DemangledTypeNode::NamedType(UnknownNamedTypeClass, ParseCompatibilityName(s, depth)); } StringList RenderSegments(const DemangledQualifiedName& name) @@ -2431,9 +2437,16 @@ BN::QualifiedName DemangledTemplateSimplifier::SimplifyQualifiedName(const BN::Q { auto renderedName = name.GetString(); auto stripped = StripLeadingTypeKeyword(std::string_view(renderedName.data(), renderedName.size())); - DemangledQualifiedName parsed = ParseCompatibilityName(stripped); - SimplifyNameSegmentsInPlace(parsed); - return BN::QualifiedName(RenderSegments(parsed)); + try + { + DemangledQualifiedName parsed = ParseCompatibilityName(stripped, 0); + SimplifyNameSegmentsInPlace(parsed); + return BN::QualifiedName(RenderSegments(parsed)); + } + catch (const CompatibilityTemplateDepthExceeded&) + { + return name; + } } bool DemangledTemplateSimplifier::NameSegmentsHaveTemplateArguments(const DemangledQualifiedName& name) diff --git a/demangler/gnu3/demangle_gnu3.cpp b/demangler/gnu3/demangle_gnu3.cpp index f53bf28323..bc666eaef5 100644 --- a/demangler/gnu3/demangle_gnu3.cpp +++ b/demangler/gnu3/demangle_gnu3.cpp @@ -20,6 +20,7 @@ #include "demangler/demangled_template_simplifier.h" #include #include +#include #include @@ -121,8 +122,15 @@ namespace { size_t len = c - '0'; while (i < raw.size() && raw[i] >= '0' && raw[i] <= '9') - len = (len * 10) + (raw[i++] - '0'); - i = std::min(raw.size(), i + len); + { + size_t digit = raw[i++] - '0'; + if (len > (std::numeric_limits::max() - digit) / 10) + return false; + len = (len * 10) + digit; + } + if (len > raw.size() - i) + return false; + i += len; } } return false; @@ -554,7 +562,10 @@ string DemangleGNU3::DemangleSourceName() { LOG_INDENTATION_SCOPE; LogWithIndentation("%s : %s\n", __FUNCTION__, m_reader.GetRaw()); - string name = EscapeDemangledName(m_reader.ReadStringView(DemangleNumber())); + int64_t length = DemangleNumber(); + if (length < 0 || static_cast(length) > std::numeric_limits::max()) + throw DemangleException(); + string name = EscapeDemangledName(m_reader.ReadStringView(static_cast(length))); m_lastName = name; return name; } @@ -974,7 +985,10 @@ DemangledTypeNode DemangleGNU3::DemangleType() case 'v': { // vector of size - uint64_t size = DemangleNumber(); + int64_t dimension = DemangleNumber(); + if (dimension < 0) + throw DemangleException(); + uint64_t size = static_cast(dimension); if (!m_reader.ConsumeIf('_')) throw DemangleException(); NodeRef childRef = nullptr; @@ -1179,12 +1193,18 @@ int64_t DemangleGNU3::DemangleNumber() if (!IsAsciiDigit(m_reader.PeekOr())) throw DemangleException(); - int64_t result = 0; + uint64_t result = 0; + const uint64_t limit = static_cast(std::numeric_limits::max()) + (negative ? 1 : 0); do { - result = result * 10 + (m_reader.Read() - '0'); + uint64_t digit = m_reader.Read() - '0'; + if (result > (limit - digit) / 10) + throw DemangleException("Demangled number exceeds 64-bit signed range"); + result = result * 10 + digit; } while (IsAsciiDigit(m_reader.PeekOr())); - return negative ? -result : result; + if (negative && result == limit) + return std::numeric_limits::min(); + return negative ? -static_cast(result) : static_cast(result); } @@ -1723,6 +1743,7 @@ DemangledTypeNode DemangleGNU3::DemangleUnresolvedType() string DemangleGNU3::DemangleExpression(DemangledTypeNode* outNode) { + NestingGuard nestingGuard(m_nestingDepth); LogWithIndentation("%s: '%s'\n", __FUNCTION__, m_reader.GetRaw()); /* ::= @@ -1962,9 +1983,11 @@ string DemangleGNU3::DemangleExpression(DemangledTypeNode* outNode) // When listNumber is out of range (e.g. fL used inside a decltype return // type before function params are known), the fallback paths below produce // a placeholder string "fp" / "fpN". - listNumber = DemangleNumber() + 1; - if (listNumber < 0 || !m_reader.ConsumeIf('p')) + int64_t encodedListNumber = DemangleNumber(); + if (encodedListNumber < 0 || encodedListNumber == std::numeric_limits::max() || + !m_reader.ConsumeIf('p')) throw DemangleException(); + listNumber = encodedListNumber + 1; } DemangleCVQualifiers(cnst, vltl, rstrct); elm = m_reader.PeekOr(); @@ -1983,7 +2006,10 @@ string DemangleGNU3::DemangleExpression(DemangledTypeNode* outNode) } else if (IsAsciiDigit(elm) || IsAsciiUpper(elm)) { - elementNum = DemangleNumber() + 1; + int64_t encodedElementNum = DemangleNumber(); + if (encodedElementNum == std::numeric_limits::max()) + throw DemangleException(); + elementNum = encodedElementNum + 1; if (!m_reader.ConsumeIf('_')) throw DemangleException(); if (elementNum < 0 || @@ -3258,18 +3284,11 @@ bool DemangleGNU3Static::DemangleGlobalHeader(string& name, string& header) if (name.empty()) return false; - size_t strippedCount = 0; - string encoded = name; - while (!encoded.empty() && encoded[0] == '_') - { - encoded.erase(0, 1); - strippedCount ++; - if (encoded.empty()) - return false; - } - - if (strippedCount == 0) + size_t strippedCount = name.find_first_not_of('_'); + if (strippedCount == 0 || strippedCount == string::npos) return false; + std::string_view encoded(name); + encoded.remove_prefix(strippedCount); static const vector> headers = { {"GLOBAL__sub_I_", "(static initializer)"}, @@ -3279,9 +3298,10 @@ bool DemangleGNU3Static::DemangleGlobalHeader(string& name, string& header) for (auto& i: headers) { - if (encoded.size() > i.first.size() && encoded.substr(0, i.first.size()) == i.first) + if (encoded.size() > i.first.size() && + encoded.compare(0, i.first.size(), i.first.data(), i.first.size()) == 0) { - name = name.substr(i.first.size() + strippedCount); + name.erase(0, i.first.size() + strippedCount); header = i.second; return true; } @@ -3302,122 +3322,164 @@ namespace Platform& platform = config.GetPlatform(); bool simplifyTemplates = config.simplifyTemplates; - // Handle _block_invoke[.N] and _block_invoke_N suffixes (Clang/Apple block invocations). - // E.g. ____ZN4dyld5_mainEPK12macho_headermiPPKcS5_S5_Pm_block_invoke.110 - // -> "invocation_function_for_block_in_dyld::_main(...)" + // LLVM recognizes a single block-invoke suffix. Allow one additional + // thread-local initializer layer for our existing compatibility behavior. + enum class SpecialSuffix { BlockInvoke, TlvInit }; + static constexpr size_t maxSpecialSuffixes = 2; + vector suffixes; + std::string_view baseName = name; + bool hasBlockInvoke = false; + bool hasTlvInit = false; static constexpr std::string_view blockInvokeSuffix = "_block_invoke"; - size_t blockPos = name.rfind(blockInvokeSuffix); - if (blockPos != std::string_view::npos) - { - // Verify the suffix is _block_invoke optionally followed by [._] only - std::string_view tail = name.substr(blockPos + blockInvokeSuffix.size()); - bool validSuffix = tail.empty(); - if (!validSuffix && (tail[0] == '.' || tail[0] == '_')) + static constexpr std::string_view tlvInitSuffix = "$tlv$init"; + + auto applySuffixes = [&](DemanglerResult& result) { + for (auto it = suffixes.rbegin(); it != suffixes.rend(); ++it) { - size_t i = 1; - while (i < tail.size() && IsAsciiDigit(tail[i])) - i++; - validSuffix = (i == tail.size() && i > 1); + if (*it == SpecialSuffix::BlockInvoke) + result.name = QualifiedName(StringList{ + "invocation_function_for_block_in_" + + JoinNameSegments(StringList(result.name.begin(), result.name.end()))}); + else if (result.name.size() > 0) + result.name[result.name.size() - 1] += "$tlv$init"; + else + result.name = QualifiedName(StringList{"$tlv$init"}); } - if (validSuffix) + }; + + auto parseEncoding = [&](std::string_view candidate, bool normalizeBlock, + bool allowImplicitThis, bool annotateSuffixes) -> std::optional { + string encoding(candidate); + if (normalizeBlock) { - // Extract the base symbol: everything before _block_invoke - std::string_view base = name.substr(0, blockPos); - // Normalize leading underscores: find 'Z' after underscores, keep one '_' before it - size_t zPos = base.find_first_not_of('_'); - if (zPos != std::string_view::npos && base[zPos] == 'Z') + // Block symbols may have extra leading underscores. Keep one + // underscore before Z, as the recursive path did. + size_t zPos = encoding.find_first_not_of('_'); + encoding = "_" + encoding.substr(zPos); + } + string header; + bool foundHeader = DemangleGNU3Static::DemangleGlobalHeader(encoding, header); + + if (!encoding.compare(0, 2, "_Z")) + encoding = encoding.substr(2); + else if (!encoding.compare(0, 3, "__Z")) + encoding = encoding.substr(3); + else if (foundHeader && !header.empty()) + { + DemanglerResult result; + StringList nameSegments{header, EscapeDemangledName(encoding)}; + result.name = QualifiedName(nameSegments); + result.type = DemangledTypeNode::NamedType(nameSegments).Finalize(platform); + if (annotateSuffixes) + applySuffixes(result); + return result; + } + else + return std::nullopt; + + try + { + thread_local ::DemangleGNU3 demangle(platform, encoding); + demangle.Reset(platform, encoding); + DemanglerResult result; + StringList nameSegments; + DemangledTypeNode type = demangle.DemangleSymbol( + nameSegments, simplifyTemplates, allowImplicitThis && !foundHeader); + // DemangleSymbol may leave trailing text after a valid parse. Only + // reject a suffix marker that it did not actually consume; other + // trailing forms retain their existing behavior. + std::string_view remaining = demangle.RemainingInput(); + if (remaining.compare(0, 2, "@@") != 0 && + (remaining.find(blockInvokeSuffix) != std::string_view::npos || + remaining.find(tlvInitSuffix) != std::string_view::npos)) + return std::nullopt; + if (simplifyTemplates) + DemangledTemplateSimplifier::SimplifyTypeNodeInPlace(type); + result.type = type.Finalize(platform); + + if (nameSegments.empty()) { - string normalized = "_"; - normalized.append(base.substr(zPos)); - if (auto baseResult = DemangleGNU3WithConfig(config, normalized, false)) + if (GetFinalizedTypeClass(result.type) == NamedTypeReferenceClass && + result.type->GetNamedTypeReference()->GetTypeReferenceClass() == UnknownNamedTypeClass) { - DemanglerResult result; - result.name = QualifiedName(StringList{ - "invocation_function_for_block_in_" + JoinNameSegments(StringList(baseResult->name.begin(), baseResult->name.end()))}); - result.type = baseResult->type; - return result; + const auto typeName = result.type->GetTypeName(); + nameSegments = StringList(typeName.begin(), typeName.end()); + result.type = nullptr; + } + else if (GetFinalizedTypeClass(result.type) == NamedTypeReferenceClass) + { + auto typeName = result.type->GetTypeName(); + if (typeName.size() > 0) + nameSegments = StringList{"_" + typeName[typeName.size() - 1]}; } } - } - } - // Handle macOS thread-local variable initializer suffix: $tlv$init - // E.g. __ZL9recursive$tlv$init -> demangle "__ZL9recursive" then annotate. - static constexpr std::string_view tlvInitSuffix = "$tlv$init"; - if (name.size() > tlvInitSuffix.size() && - name.compare(name.size() - tlvInitSuffix.size(), tlvInitSuffix.size(), tlvInitSuffix) == 0) - { - std::string_view base = name.substr(0, name.size() - tlvInitSuffix.size()); - if (auto result = DemangleGNU3WithConfig(config, base, recoverImplicitThis)) - { - if (result->name.size() > 0) - result->name[result->name.size() - 1] += "$tlv$init"; - else - result->name = QualifiedName(StringList{"$tlv$init"}); + if (foundHeader && !header.empty()) + nameSegments.insert(nameSegments.begin(), header); + result.name = QualifiedName(nameSegments); + if (annotateSuffixes) + applySuffixes(result); return result; } - } - - string encoding(name); - string header; - bool foundHeader = DemangleGNU3Static::DemangleGlobalHeader(encoding, header); + catch (DemangleException& e) + { + LogDebugF("GNU3 demangling failed {:?}: {}", name, e.what()); + } + catch (std::exception& e) + { + LogDebugF("GNU3 demangling failed {:?}: {}", name, e.what()); + } + return std::nullopt; + }; - if (!encoding.compare(0, 2, "_Z")) - encoding = encoding.substr(2); - else if (!encoding.compare(0, 3, "__Z")) - encoding = encoding.substr(3); - else if (foundHeader && !header.empty()) - { - DemanglerResult result; - StringList nameSegments{header, EscapeDemangledName(encoding)}; - result.name = QualifiedName(nameSegments); - result.type = DemangledTypeNode::NamedType(nameSegments).Finalize(platform); + // A length-prefixed source name may itself contain suffix-looking text. + // Give the complete encoding precedence before trying extensions. + if (auto result = parseEncoding(name, false, recoverImplicitThis, false)) return result; - } - else - return std::nullopt; - try + for (size_t suffixCount = 0; suffixCount < maxSpecialSuffixes; suffixCount++) { - thread_local ::DemangleGNU3 demangle(platform, encoding); - demangle.Reset(platform, encoding); - DemanglerResult result; - StringList nameSegments; - DemangledTypeNode type = demangle.DemangleSymbol( - nameSegments, simplifyTemplates, recoverImplicitThis && !foundHeader); - if (simplifyTemplates) - DemangledTemplateSimplifier::SimplifyTypeNodeInPlace(type); - result.type = type.Finalize(platform); - - if (nameSegments.empty()) + bool peeled = false; + // Clang/Apple block invocation suffixes may end in .N or _N. + size_t blockPos = baseName.rfind(blockInvokeSuffix); + if (blockPos != std::string_view::npos) { - if (GetFinalizedTypeClass(result.type) == NamedTypeReferenceClass && - result.type->GetNamedTypeReference()->GetTypeReferenceClass() == UnknownNamedTypeClass) + std::string_view tail = baseName.substr(blockPos + blockInvokeSuffix.size()); + bool validSuffix = tail.empty(); + if (!validSuffix && (tail[0] == '.' || tail[0] == '_')) { - const auto typeName = result.type->GetTypeName(); - nameSegments = StringList(typeName.begin(), typeName.end()); - result.type = nullptr; + size_t i = 1; + while (i < tail.size() && IsAsciiDigit(tail[i])) + i++; + validSuffix = (i == tail.size() && i > 1); } - else if (GetFinalizedTypeClass(result.type) == NamedTypeReferenceClass) + std::string_view blockBase = baseName.substr(0, blockPos); + size_t zPos = blockBase.find_first_not_of('_'); + if (validSuffix && zPos != std::string_view::npos && blockBase[zPos] == 'Z') { - auto typeName = result.type->GetTypeName(); - if (typeName.size() > 0) - nameSegments = StringList{"_" + typeName[typeName.size() - 1]}; + if (hasBlockInvoke) + return std::nullopt; + baseName = blockBase; + suffixes.push_back(SpecialSuffix::BlockInvoke); + hasBlockInvoke = true; + peeled = true; } } - - if (foundHeader && !header.empty()) - nameSegments.insert(nameSegments.begin(), header); - result.name = QualifiedName(nameSegments); - return result; - } - catch (DemangleException& e) - { - LogDebugF("GNU3 demangling failed {:?}: {}", name, e.what()); - } - catch (std::exception& e) - { - LogDebugF("GNU3 demangling failed {:?}: {}", name, e.what()); + if (!peeled && baseName.size() > tlvInitSuffix.size() && + baseName.compare(baseName.size() - tlvInitSuffix.size(), tlvInitSuffix.size(), tlvInitSuffix) == 0) + { + if (hasTlvInit) + return std::nullopt; + baseName.remove_suffix(tlvInitSuffix.size()); + suffixes.push_back(SpecialSuffix::TlvInit); + hasTlvInit = true; + peeled = true; + } + if (!peeled) + break; + if (auto result = parseEncoding(baseName, hasBlockInvoke, + recoverImplicitThis && !hasBlockInvoke, true)) + return result; } return std::nullopt; } diff --git a/demangler/gnu3/demangle_gnu3.h b/demangler/gnu3/demangle_gnu3.h index 3c31c369ca..d830605e68 100644 --- a/demangler/gnu3/demangle_gnu3.h +++ b/demangler/gnu3/demangle_gnu3.h @@ -94,7 +94,9 @@ class DemangleGNU3 using NodeRefList = _STD_VECTOR; static constexpr size_t MAX_DEMANGLE_NODE_LENGTH = 8192; - static constexpr size_t MAX_DEMANGLE_NESTING_DEPTH = 1024; + // Analysis workers can have 512 KiB stacks. Each grammar level can use + // several parser and type-node frames, so leave room for finalization. + static constexpr size_t MAX_DEMANGLE_NESTING_DEPTH = 128; _STD_STRING m_mangledName; DemangleReader m_reader{m_mangledName, MAX_DEMANGLE_NODE_LENGTH, false}; std::reference_wrapper m_platform; @@ -170,6 +172,10 @@ class DemangleGNU3 void Reset(BN::Platform& platform, _STD_STRING mangledName); DemangledTypeNode DemangleSymbol( StringList& varName, bool simplifyTemplates = false, bool recoverImplicitThis = true); + [[nodiscard]] std::string_view RemainingInput() const + { + return std::string_view(m_reader.GetRaw(), m_reader.Length()); + } }; diff --git a/demangler/msvc/demangle_msvc.cpp b/demangler/msvc/demangle_msvc.cpp index af087985bb..019b38195c 100644 --- a/demangler/msvc/demangle_msvc.cpp +++ b/demangler/msvc/demangle_msvc.cpp @@ -243,6 +243,7 @@ void Demangle::Reset(const DemanglerConfig& config, const _STD_STRING& mangledNa m_config = config; m_templateParamDepth = 0; m_nestingDepth = 0; + m_totalArrayDimensions = 0; } @@ -287,6 +288,22 @@ Platform& Demangle::GetRenderingPlatform() const return m_config.GetPlatform(); } +DemangledTypeNode Demangle::CheckedArrayType(DemangledTypeNode elementType, const _STD_VECTOR& extents) +{ + uint64_t width = elementType.Finalize(GetRenderingPlatform())->GetWidth(); + for (uint64_t extent : std::views::reverse(extents)) + { + // The core type printer interprets array counts as signed, and Type::GetWidth + // multiplies counts by child widths without checking for overflow. + if (extent > static_cast(std::numeric_limits::max()) || + (width != 0 && extent > std::numeric_limits::max() / width)) + throw DemangleException("Array extent exceeds supported range"); + width *= extent; + elementType = DemangledTypeNode::ArrayType(std::move(elementType), extent); + } + return elementType; +} + DemangledTypeNode Demangle::DemangleReferencedSymbolValue(BackrefList& varList) { MSVC_TRACE_SCOPE; @@ -347,6 +364,12 @@ DemangledTypeNode Demangle::DemangleVarType(BackrefList& varList, bool isReturn, uint64_t dimensionCount = DecodeEncodedUnsignedNumber(); if (dimensionCount > static_cast(m_reader.Length())) throw DemangleException("Array dimension count is too large"); + // Array extents become nested type nodes even though parsing them does + // not recurse. Finalizing those nodes does recurse, so budget dimensions + // across the entire symbol (including arrays reached through backrefs). + if (dimensionCount > MAX_DEMANGLE_NESTING_DEPTH - m_totalArrayDimensions) + throw DemangleException("Array dimension count is too large"); + m_totalArrayDimensions += static_cast(dimensionCount); _STD_VECTOR elementList; for (uint64_t i = 0; i < dimensionCount; i++) @@ -429,15 +452,13 @@ DemangledTypeNode Demangle::DemangleVarType(BackrefList& varList, bool isReturn, { // Multi-dimensional array type: Y...@ _STD_VECTOR elementList = demangleArrayExtents(); - newType = DemangleVarType(varList, false); - for (uint64_t i : std::views::reverse(elementList)) - { - newType = DemangledTypeNode::ArrayType(std::move(newType), i); - } + newType = CheckedArrayType(DemangleVarType(varList, false), elementList); recordTypeBackref(newType); return newType; } - case 'Z': return DemangledTypeNode::VarArgsType(); + // The trailing varargs marker is handled by DemangleVariableList. It is + // never a standalone type (including data, return, and template types). + case 'Z': throw DemangleException("Varargs is not a standalone type"); case '?': { char next = m_reader.PeekOr(); @@ -689,12 +710,7 @@ DemangledTypeNode Demangle::DemangleVarType(BackrefList& varList, bool isReturn, { MSVC_TRACE("Demangle multi-dimensions array"); _STD_VECTOR elementList = demangleArrayExtents(); - child = DemangleVarType(varList, false); - - for (uint64_t i : std::views::reverse(elementList)) - { - child = DemangledTypeNode::ArrayType(std::move(child), i); - } + child = CheckedArrayType(DemangleVarType(varList, false), elementList); } else { @@ -1410,6 +1426,8 @@ DemangledTypeNode Demangle::DemangleString(NameList& symbolName) // Length is just a number uint64_t length = DecodeEncodedUnsignedNumber(); + if (length > static_cast(std::numeric_limits::max())) + throw DemangleException("String literal array length exceeds supported range"); MSVC_TRACE("{}: Before CRC32 '{}'", __FUNCTION__, m_reader.GetRaw()); diff --git a/demangler/msvc/demangle_msvc.h b/demangler/msvc/demangle_msvc.h index 6ea2d317a1..df0b4c7b9b 100644 --- a/demangler/msvc/demangle_msvc.h +++ b/demangler/msvc/demangle_msvc.h @@ -147,6 +147,7 @@ class Demangle BN::DemanglerConfig m_config; size_t m_templateParamDepth = 0; size_t m_nestingDepth = 0; + size_t m_totalArrayDimensions = 0; // The largest observed depth in a real-world corpus of roughly 200k MSVC symbols was 54. static constexpr size_t MAX_DEMANGLE_NESTING_DEPTH = 256; using NestingGuard = DemangleNestingGuard; @@ -163,6 +164,7 @@ class Demangle bool TryAppendLocalScopeAt(NameList& nameList, BackrefList& nameBackrefList, const char* encodedNumberStart, bool typeNameContext); [[nodiscard]] _STD_STRING FormatTypeAndName(const DemangledTypeNode& type, const NameList& name) const; + DemangledTypeNode CheckedArrayType(DemangledTypeNode elementType, const _STD_VECTOR& extents); enum class TypeBackrefMode { RecordTopLevel, diff --git a/plugins/workflow_swift/src/demangler/function_type.rs b/plugins/workflow_swift/src/demangler/function_type.rs index 73abe23fad..fbee1f1878 100644 --- a/plugins/workflow_swift/src/demangler/function_type.rs +++ b/plugins/workflow_swift/src/demangler/function_type.rs @@ -232,7 +232,7 @@ pub fn build_function_type(symbol: &Symbol, arch: &CoreArchitecture) -> Option Option>>()?; - let ret_type = sig - .return_type() - .and_then(|rt| rt.to_bn_type(arch)) - .unwrap_or_else(Type::void); + let ret_type = match sig.return_type() { + Some(rt) => rt.to_bn_type(arch)?, + None => Type::void(), + }; Some(cc.build_type(&ret_type, params)) } diff --git a/plugins/workflow_swift/src/demangler/type_reconstruction.rs b/plugins/workflow_swift/src/demangler/type_reconstruction.rs index 33e44a72ae..9486888a40 100644 --- a/plugins/workflow_swift/src/demangler/type_reconstruction.rs +++ b/plugins/workflow_swift/src/demangler/type_reconstruction.rs @@ -7,10 +7,23 @@ use swift_demangler::{TypeKind, TypeRef}; pub(crate) trait TypeRefExt { fn to_bn_type(&self, arch: &CoreArchitecture) -> Option>; + fn to_bn_type_with_depth(&self, arch: &CoreArchitecture, depth: usize) -> Option>; } +// Bound recursive conversion even when a mangled name has many nested wrappers. +const MAX_SWIFT_TYPE_DEPTH: usize = 16; + impl TypeRefExt for TypeRef<'_> { fn to_bn_type(&self, arch: &CoreArchitecture) -> Option> { + self.to_bn_type_with_depth(arch, 0) + } + + fn to_bn_type_with_depth(&self, arch: &CoreArchitecture, depth: usize) -> Option> { + if depth >= MAX_SWIFT_TYPE_DEPTH { + return None; + } + let next_depth = depth + 1; + match self.kind() { TypeKind::Named(named) => { let name = named.name()?; @@ -53,8 +66,8 @@ impl TypeRefExt for TypeRef<'_> { let params: Vec<_> = func_type .parameters() .iter() - .filter_map(|p| { - let ty = p.type_ref.to_bn_type(arch)?; + .map(|p| { + let ty = p.type_ref.to_bn_type_with_depth(arch, next_depth)?; let name = p.label.unwrap_or("").to_string(); Some(binaryninja::types::FunctionParameter { ty: ty.into(), @@ -62,12 +75,12 @@ impl TypeRefExt for TypeRef<'_> { location: ValueLocationSource::Default, }) }) - .collect(); + .collect::>>()?; - let ret_type = func_type - .return_type() - .and_then(|rt| rt.to_bn_type(arch)) - .unwrap_or_else(Type::void); + let ret_type = match func_type.return_type() { + Some(rt) => rt.to_bn_type_with_depth(arch, next_depth)?, + None => Type::void(), + }; Some(Type::function(&ret_type, params, false)) } @@ -101,7 +114,7 @@ impl TypeRefExt for TypeRef<'_> { } TypeKind::InOut(inner) => { - let inner_ty = inner.to_bn_type(arch)?; + let inner_ty = inner.to_bn_type_with_depth(arch, next_depth)?; Some(Type::pointer(arch, &inner_ty)) } @@ -132,13 +145,17 @@ impl TypeRefExt for TypeRef<'_> { | TypeKind::Owned(inner) | TypeKind::Sending(inner) | TypeKind::Isolated(inner) - | TypeKind::NoDerivative(inner) => inner.to_bn_type(arch), + | TypeKind::NoDerivative(inner) => inner.to_bn_type_with_depth(arch, next_depth), - TypeKind::Weak(inner) | TypeKind::Unowned(inner) => inner.to_bn_type(arch), + TypeKind::Weak(inner) | TypeKind::Unowned(inner) => { + inner.to_bn_type_with_depth(arch, next_depth) + } - TypeKind::DynamicSelf(inner) => inner.to_bn_type(arch), + TypeKind::DynamicSelf(inner) => inner.to_bn_type_with_depth(arch, next_depth), - TypeKind::ConstrainedExistential(inner) => inner.to_bn_type(arch), + TypeKind::ConstrainedExistential(inner) => { + inner.to_bn_type_with_depth(arch, next_depth) + } TypeKind::Any => { // Swift.Any is an existential container (pointer-sized at the ABI level). @@ -147,26 +164,16 @@ impl TypeRefExt for TypeRef<'_> { TypeKind::Existential(protocols) => { if protocols.len() == 1 { - protocols[0].to_bn_type(arch) + protocols[0].to_bn_type_with_depth(arch, next_depth) } else { None } } - TypeKind::Generic { inner, .. } => inner.to_bn_type(arch), - - // Types we can't meaningfully represent. - TypeKind::Error - | TypeKind::Builtin(_) - | TypeKind::BuiltinFixedArray { .. } - | TypeKind::ImplFunction(_) - | TypeKind::Pack(_) - | TypeKind::ValueGeneric(_) - | TypeKind::CompileTimeLiteral(_) - | TypeKind::AssociatedType { .. } - | TypeKind::Opaque { .. } - | TypeKind::SILBox { .. } - | TypeKind::Other(_) => None, + TypeKind::Generic { inner, .. } => inner.to_bn_type_with_depth(arch, next_depth), + + // Leave unsupported types, including kinds added by the demangler, unconverted. + _ => None, } } } @@ -204,3 +211,82 @@ pub(crate) fn make_named_type_ref(module: Option<&str>, name: &str) -> Ref let ntr = NamedTypeReference::new(NamedTypeReferenceClass::UnknownNamedTypeClass, qname); Type::named_type(&ntr) } + +#[cfg(test)] +mod tests { + use super::*; + use binaryninja::architecture::ArchitectureExt; + use binaryninja::demangle::{CustomDemangler, DemanglerConfig}; + use swift_demangler::{Context, HasFunctionSignature, Symbol}; + + use crate::demangler::SwiftDemangler; + + #[test] + fn deeply_nested_inout_type_is_rejected() { + let _session = crate::test_session(); + let arch = CoreArchitecture::by_name("aarch64").expect("aarch64 architecture"); + + for (depth, expected) in [(2, true), (MAX_SWIFT_TYPE_DEPTH, false), (1_000, false)] { + let mangled = format!("$s4main1fyySi{}F", "z".repeat(depth)); + let ctx = Context::new(); + let Some(symbol) = Symbol::parse(&ctx, &mangled) else { + // Newer demanglers can reject the deepest tree before type conversion. + assert_eq!(depth, 1_000); + continue; + }; + let Symbol::Function(function) = &symbol else { + panic!("expected a function symbol"); + }; + let signature = function.signature().expect("function signature"); + let parameters = signature.parameters(); + assert_eq!(parameters.len(), 1); + assert_eq!(parameters[0].type_ref.to_bn_type(&arch).is_some(), expected); + } + } + + #[test] + fn deep_parameter_does_not_produce_a_partial_function_type() { + let _session = crate::test_session(); + let arch = CoreArchitecture::by_name("aarch64").expect("aarch64 architecture"); + let platform = arch.standalone_platform().expect("aarch64 platform"); + let config = DemanglerConfig::for_platform(&platform, false); + let demangler = SwiftDemangler; + + let shallow = "$s4main1fyySizF"; + let deep = format!("$s4main1fyySi{}F", "z".repeat(MAX_SWIFT_TYPE_DEPTH)); + assert!(demangler + .demangle(shallow, &config) + .expect("shallow symbol") + .ty + .is_some()); + assert!(demangler + .demangle(&deep, &config) + .expect("deep symbol") + .ty + .is_none()); + } + + #[test] + fn deep_nested_function_parameter_does_not_disappear() { + let _session = crate::test_session(); + let arch = CoreArchitecture::by_name("aarch64").expect("aarch64 architecture"); + let ctx = Context::new(); + let shallow = Symbol::parse(&ctx, "$s4main1fyyySizXEF").expect("function symbol"); + let Symbol::Function(function) = &shallow else { + panic!("expected a function symbol"); + }; + let params = function.signature().expect("signature").parameters(); + assert!(matches!(params[0].type_ref.kind(), TypeKind::Function(_))); + assert!(params[0].type_ref.to_bn_type(&arch).is_some()); + + let mangled = format!("$s4main1fyyySi{}XEF", "z".repeat(MAX_SWIFT_TYPE_DEPTH)); + let deep_ctx = Context::new(); + let symbol = Symbol::parse(&deep_ctx, &mangled).expect("deep symbol"); + let Symbol::Function(function) = &symbol else { + panic!("expected a function symbol"); + }; + let params = function.signature().expect("signature").parameters(); + assert!(matches!(params[0].type_ref.kind(), TypeKind::Function(_))); + assert!(params[0].type_ref.to_bn_type(&arch).is_none()); + } +} diff --git a/plugins/workflow_swift/src/lib.rs b/plugins/workflow_swift/src/lib.rs index 911770ae1a..523edf3469 100644 --- a/plugins/workflow_swift/src/lib.rs +++ b/plugins/workflow_swift/src/lib.rs @@ -7,6 +7,14 @@ use demangler::SwiftDemangler; pub const SETTING_EXTRACT_TYPES: &str = "analysis.swift.extractTypesFromMangledNames"; +#[cfg(test)] +fn test_session() -> &'static binaryninja::headless::Session { + use std::sync::OnceLock; + + static SESSION: OnceLock = OnceLock::new(); + SESSION.get_or_init(|| binaryninja::headless::Session::new().expect("headless session")) +} + #[no_mangle] #[allow(non_snake_case)] pub extern "C" fn CorePluginDependencies() { diff --git a/python/demangle.py b/python/demangle.py index 326d018fa5..b2e048f3f0 100644 --- a/python/demangle.py +++ b/python/demangle.py @@ -265,6 +265,8 @@ def _demangle_with_demangler( try: return DemangleResult._from_core_struct(result) + except UnicodeDecodeError: + return None finally: core.BNFreeDemanglerResult(result) @@ -441,10 +443,10 @@ class Demangler(metaclass=_DemanglerMetaclass): name = None _registered_demanglers = [] - _cached_name = None def __init__(self, handle=None): self._uses_legacy_demangle_signature = False + self._pending_results: dict[int, core.BNDemanglerResult] = {} if handle is not None: self.handle = core.handle_of_type(handle, core.BNDemangler) self.__dict__["name"] = core.BNGetDemanglerName(handle) @@ -546,13 +548,18 @@ def _demangle(self, ctxt, name, config, result): if not isinstance(var_name, types.QualifiedName): var_name = types.QualifiedName(var_name) - Demangler._cached_name = core.BNDemanglerResult() - Demangler._cached_name.name = var_name._to_core_struct() - if type is not None: - Demangler._cached_name.type = core.BNNewTypeReference(type.handle) - else: - Demangler._cached_name.type = None - result[0] = Demangler._cached_name + cached_result = core.BNDemanglerResult() + cached_result.name = var_name._to_core_struct() + try: + if type is not None: + cached_result.type = core.BNNewTypeReference(type.handle) + result[0] = cached_result + self._pending_results[ctypes.addressof(result.contents)] = cached_result + except Exception: + if cached_result.type: + core.BNFreeType(cached_result.type) + result[0] = core.BNDemanglerResult() + raise return True except Exception: log_error_for_exception("Unhandled Python exception in Demangler._demangle") @@ -560,9 +567,10 @@ def _demangle(self, ctxt, name, config, result): def _free_result(self, ctxt, result): try: - if result is not None and result.contents.type: - core.BNFreeType(result.contents.type) - Demangler._cached_name = None + if result is not None: + cached_result = self._pending_results.pop(ctypes.addressof(result.contents), None) + if result.contents.type: + core.BNFreeType(result.contents.type) except Exception: log_error_for_exception("Unhandled Python exception in Demangler._free_result") @@ -639,5 +647,7 @@ def demangle(self, name: str, config: DemanglerConfig) -> Optional[DemangleResul try: return DemangleResult._from_core_struct(result) + except UnicodeDecodeError: + return None finally: core.BNFreeDemanglerResult(result) diff --git a/rust/src/demangle.rs b/rust/src/demangle.rs index ee76c16885..3c097dbb14 100644 --- a/rust/src/demangle.rs +++ b/rust/src/demangle.rs @@ -15,7 +15,7 @@ //! Interfaces for demangling and simplifying mangled names in binaries. use binaryninjacore_sys::*; -use std::ffi::{c_char, c_void}; +use std::ffi::{c_char, c_void, CString}; use std::ptr; use crate::architecture::{ArchitectureExt, CoreArchitecture}; @@ -23,7 +23,7 @@ use crate::binary_view::BinaryView; use crate::platform::Platform; use crate::qualified_name::QualifiedName; use crate::rc::*; -use crate::string::{raw_to_string, BnString, IntoCStr}; +use crate::string::{raw_to_string, BnString}; use crate::types::Type; pub type Result = std::result::Result; @@ -189,7 +189,7 @@ fn demangle_with_demangler( return None; } - let mangled_name = mangled_name.to_cstr(); + let mangled_name = CString::new(mangled_name).ok()?; let api_config = config.to_api_object(); let mut result = BNDemanglerResult::default(); let res = unsafe { @@ -199,7 +199,7 @@ fn demangle_with_demangler( } pub fn demangle_any(mangled_name: &str, config: &DemanglerConfig) -> Option { - let mangled_name = mangled_name.to_cstr(); + let mangled_name = CString::new(mangled_name).ok()?; let api_config = config.to_api_object(); let mut result = BNDemanglerResult::default(); let res = unsafe { BNDemangle(mangled_name.as_ptr(), &api_config, &mut result) }; @@ -309,7 +309,9 @@ impl Demangler { } pub fn is_mangled_string(&self, name: &str) -> bool { - let bytes = name.to_cstr(); + let Ok(bytes) = CString::new(name) else { + return false; + }; unsafe { BNIsDemanglerMangledName(self.handle, bytes.as_ref().as_ptr() as *const _) } } @@ -322,7 +324,7 @@ impl Demangler { } pub fn from_name(name: &str) -> Option { - let name_bytes = name.to_cstr(); + let name_bytes = CString::new(name).ok()?; let demangler = unsafe { BNGetDemanglerByName(name_bytes.as_ref().as_ptr() as *const _) }; if demangler.is_null() { None @@ -332,6 +334,10 @@ impl Demangler { } pub fn register(name: &str, demangler: C) -> bool { + let Ok(name) = CString::new(name) else { + return false; + }; + extern "C" fn cb_is_mangled_string(ctxt: *mut c_void, name: *const c_char) -> bool where C: CustomDemangler, @@ -385,7 +391,6 @@ impl Demangler { }) } - let name = name.to_cstr(); let name_ptr = name.as_ptr(); let ctxt = Box::into_raw(Box::new(demangler)); diff --git a/rust/tests/demangler.rs b/rust/tests/demangler.rs index 4f2f91a4a1..b59640add3 100644 --- a/rust/tests/demangler.rs +++ b/rust/tests/demangler.rs @@ -80,6 +80,27 @@ fn test_demangler_simple() { assert_eq!(msvc_type.get_string_after_name(None), "(int32_t)"); } +#[test] +fn test_embedded_nul_is_a_demangle_failure() { + let _session = session(); + let arch = CoreArchitecture::by_name("x86_64").expect("x86_64 exists"); + let platform = arch.standalone_platform().expect("x86_64 platform exists"); + let config = DemanglerConfig::for_platform(&platform, false); + let itanium_name = "_Z3foov\0suffix"; + + assert!(demangle_any(itanium_name, &config).is_none()); + assert!(demangle_generic(&arch, itanium_name, None, false).is_none()); + assert!(demangle_llvm(itanium_name, false).is_none()); + assert!(demangle_gnu3(&arch, itanium_name, false).is_none()); + assert!(demangle_ms(&arch, "?baz@@YAHH@Z\0suffix", false).is_none()); + + let llvm = Demangler::from_name("LLVM").expect("LLVM demangler exists"); + assert!(!llvm.is_mangled_string(itanium_name)); + assert!(llvm.demangle(itanium_name, &config).is_none()); + assert!(Demangler::from_name("LLVM\0suffix").is_none()); + assert!(!Demangler::register("test\0name", TestDemangler)); +} + #[test] fn test_simplify_demangled_template_name() { let _session = session();