From b24db0eaa5f5ed7f55cb521f52d03d976673e158 Mon Sep 17 00:00:00 2001 From: xirvik Date: Wed, 12 Aug 2026 15:50:56 +0000 Subject: [PATCH] Do not dereference missing text nodes in the tinyxml2 backend. Empty elements and element children crashed the parser on a null. --- src/rpc/xmlrpc_tinyxml2.cc | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/rpc/xmlrpc_tinyxml2.cc b/src/rpc/xmlrpc_tinyxml2.cc index 7ef6098b..099f6fbd 100644 --- a/src/rpc/xmlrpc_tinyxml2.cc +++ b/src/rpc/xmlrpc_tinyxml2.cc @@ -49,13 +49,23 @@ element_access(const tinyxml2::XMLElement* elem, std::initializer_listToText(); + + if (text == nullptr) + throw rpc_error(XMLRPC_TYPE_ERROR, "expected a text value"); + + return text->Value(); +} + long long element_to_int(const tinyxml2::XMLNode* elem) { char* pos; if (elem->FirstChild() == nullptr) { throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse empty integer"); } - auto str = elem->FirstChild()->ToText()->Value(); + auto str = element_text_value(elem->FirstChild()); auto result = std::strtoll(str, &pos, 10); if (pos == str || *pos != '\0') throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value"); @@ -85,7 +95,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) { if (child_element == nullptr) return torrent::Object(""); - return torrent::Object(child_element->ToText()->Value()); + return torrent::Object(element_text_value(child_element)); } else if (std::strncmp(root_type, "int", sizeof("int")) == 0 || std::strncmp(root_type, "i4", sizeof("i4")) == 0 || @@ -98,7 +108,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) { if (child_element == nullptr) throw rpc_error(XMLRPC_TYPE_ERROR, "empty boolean element"); - auto boolean_text = std::string(child_element->ToText()->Value()); + auto boolean_text = std::string(element_text_value(child_element)); if (boolean_text == "1") return torrent::Object((int64_t)1); @@ -129,7 +139,12 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) { if (name_element == nullptr) throw rpc_error(XMLRPC_PARSE_ERROR, "struct member missing name element"); - map[name_element->GetText()] = std::move(xml_value_to_object(child->FirstChildElement("value"))); + auto name_text = name_element->GetText(); + + if (name_text == nullptr) + throw rpc_error(XMLRPC_PARSE_ERROR, "struct member has an empty name element"); + + map[name_text] = std::move(xml_value_to_object(child->FirstChildElement("value"))); } return map_raw; @@ -140,7 +155,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) { if (child_element == nullptr) return torrent::Object(""); - return torrent::Object(utils::decode_base64(utils::remove_newlines(child_element->ToText()->Value()))); + return torrent::Object(utils::decode_base64(utils::remove_newlines(element_text_value(child_element)))); } else { throw rpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type)); @@ -300,6 +315,9 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer if (doc->FirstChildElement("methodCall")->FirstChildElement("methodName") == nullptr) throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element not found"); auto method_name = doc->FirstChildElement("methodCall")->FirstChildElement("methodName")->GetText(); + + if (method_name == nullptr) + throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element is empty"); torrent::Object result; // Add a shim here for system.multicall to allow better code reuse, and @@ -310,6 +328,9 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"}); for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) { auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText(); + + if (sub_method_name == nullptr) + throw rpc_error(XMLRPC_PARSE_ERROR, "multicall methodName element is empty"); // If sub_params ends up a nullptr at the end of this if-chian, // execute_command will turn it into an empty list auto sub_params = element_access(child, {"struct", "member"});