Merge pull request #1949 from sldevel/xmlrpc-crash-fix

Fix for crash in XMLRPC reply decoding on login with large inventories
meow-7.2.2
Andrey Lihatskiy 2024-07-09 01:17:22 +03:00 committed by GitHub
commit a5a7c7c8f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 267 additions and 252 deletions

View File

@ -1018,153 +1018,6 @@ const LLSD::String& LLSD::asStringRef() const { return safe(impl).asStringRef();
LLSD::String LLSD::asXMLRPCValue() const { return "<value>" + safe(impl).asXMLRPCValue() + "</value>"; }
static bool inline check(bool condition, const char* warning_message)
{
if (!condition)
{
LL_WARNS() << warning_message << LL_ENDL;
}
return condition;
}
static bool parseXMLRPCArrayValue(LLSD& target, LLSD::TreeNode* node)
{
LLSD::TreeNode* data = node->getFirstChild();
if (!check(data, "No array inner XML element (<data> expected)") ||
!check(data->hasName("data"), "Invalid array inner XML element (<data> expected)") ||
!check(!data->getNextSibling(), "Multiple array inner XML elements (single <data> expected)"))
return false;
for (LLSD::TreeNode* item = data->getFirstChild(); item; item = item->getNextSibling())
{
LLSD value;
if (!value.fromXMLRPCValue(item))
return false;
target.append(value);
}
return true;
}
static bool parseXMLRPCStructValue(LLSD& target, LLSD::TreeNode* node)
{
for (LLSD::TreeNode* item = node->getFirstChild(); item; item = item->getNextSibling())
{
if (!check(item->hasName("member"), "Invalid struct inner XML element (<member> expected)"))
return false;
std::string name;
LLSD value;
for (LLSD::TreeNode* subitem = item->getFirstChild(); subitem; subitem = subitem->getNextSibling())
{
if (subitem->hasName("name"))
{
name = LLStringFn::xml_decode(subitem->getTextContents());
}
else if (!value.fromXMLRPCValue(subitem))
{
return false;
}
}
if (!check(!name.empty(), "Empty struct member name"))
return false;
target.insert(name, value);
}
return true;
}
bool LLSD::fromXMLRPCValue(TreeNode* node)
{
clear();
llassert(node);
if (!node)
return false;
if (!check(node->hasName("value"), "Invalid XML element (<value> expected)"))
return false;
TreeNode* inner = node->getFirstChild();
if (!inner)
{
check(false, "No inner XML element (value type expected)");
// Value with no type qualifier is treated as string
assign(LLStringFn::xml_decode(node->getTextContents()));
return true;
}
if (!check(!inner->getNextSibling(), "Multiple inner XML elements (single expected)"))
return false;
if (inner->hasName("string"))
{
assign(LLStringFn::xml_decode(inner->getTextContents()));
return true;
}
if (inner->hasName("int") || inner->hasName("i4"))
{
assign(std::stoi(inner->getTextContents()));
return true;
}
if (inner->hasName("double"))
{
assign(std::stod(inner->getTextContents()));
return true;
}
if (inner->hasName("boolean"))
{
assign(!!std::stoi(inner->getTextContents()));
return true;
}
if (inner->hasName("dateTime.iso8601"))
{
assign(Date(inner->getTextContents()));
return true;
}
if (inner->hasName("base64"))
{
std::string decoded = LLBase64::decodeAsString(inner->getTextContents());
Binary binary(decoded.size());
memcpy(binary.data(), decoded.data(), decoded.size());
assign(binary);
return true;
}
if (inner->hasName("array"))
{
if (!parseXMLRPCArrayValue(*this, inner))
{
clear();
return false;
}
return true;
}
if (inner->hasName("struct"))
{
if (!parseXMLRPCStructValue(*this, inner))
{
clear();
return false;
}
return true;
}
check(false, "Unknown inner XML element (known value type expected)");
// Value with unknown type qualifier is treated as string
assign(LLStringFn::xml_decode(inner->getTextContents()));
return true;
}
// const char * helpers
LLSD::LLSD(const char* v) : impl(0) { ALLOC_LLSD_OBJECT; assign(v); }
void LLSD::assign(const char* v)

View File

@ -290,16 +290,6 @@ public:
// See http://xmlrpc.com/spec.md
String asXMLRPCValue() const;
struct TreeNode
{
virtual bool hasName(const String& name) const = 0;
virtual String getTextContents() const = 0;
virtual TreeNode* getFirstChild() const = 0;
virtual TreeNode* getNextSibling() const = 0;
};
bool fromXMLRPCValue(TreeNode* node);
operator Boolean() const { return asBoolean(); }
operator Integer() const { return asInteger(); }
operator Real() const { return asReal(); }

View File

@ -38,7 +38,9 @@
#include "v3math.h"
#include "v3dmath.h"
#include "v4math.h"
#include "llbase64.h"
#include "llquaternion.h"
#include "llsd.h"
#include "llstring.h"
#include "lluuid.h"
#include "lldir.h"
@ -3263,3 +3265,171 @@ S32 LLXMLNode::getLineNumber()
{
return mLineNumber;
}
bool LLXMLNode::parseXmlRpcArrayValue(LLSD& target)
{
LLXMLNode* datap = getFirstChild().get();
if (!datap)
{
LL_WARNS() << "No inner XML element." << LL_ENDL;
return false;
}
if (!datap->hasName("data"))
{
LL_WARNS() << "No inner XML element (<data> expected, got: "
<< datap->mName->mString << ")" << LL_ENDL;
return false;
}
if (datap->getNextSibling().get())
{
LL_WARNS() << "Multiple inner XML elements (single <data> expected)"
<< LL_ENDL;
return false;
}
for (LLXMLNode* itemp = datap->getFirstChild().get(); itemp;
itemp = itemp->getNextSibling().get())
{
LLSD value;
if (!itemp->fromXMLRPCValue(value))
{
return false;
}
target.append(value);
}
return true;
}
bool LLXMLNode::parseXmlRpcStructValue(LLSD& target)
{
std::string name;
LLSD value;
for (LLXMLNode* itemp = getFirstChild().get(); itemp;
itemp = itemp->getNextSibling().get())
{
if (!itemp->hasName("member"))
{
LL_WARNS() << "Invalid inner XML element (<member> expected, got: <"
<< itemp->mName->mString << ">" << LL_ENDL;
return false;
}
name.clear();
value.clear();
for (LLXMLNode* chilp = itemp->getFirstChild().get(); chilp;
chilp = chilp->getNextSibling().get())
{
if (chilp->hasName("name"))
{
name = LLStringFn::xml_decode(chilp->getTextContents());
}
else if (!chilp->fromXMLRPCValue(value))
{
return false;
}
}
if (name.empty())
{
LL_WARNS() << "Empty struct member name" << LL_ENDL;
return false;
}
target.insert(name, value);
}
return true;
}
bool LLXMLNode::fromXMLRPCValue(LLSD& target)
{
target.clear();
if (!hasName("value"))
{
LL_WARNS() << "Invalid XML element (<value> expected), got: <"
<< mName->mString << ">" << LL_ENDL;
return false;
}
LLXMLNode* childp = getFirstChild().get();
if (!childp)
{
LL_WARNS() << "No inner XML element (value type expected)" << LL_ENDL;
// Value with no type qualifier is treated as string
target.assign(LLStringFn::xml_decode(getTextContents()));
return true;
}
if (childp->getNextSibling())
{
LL_WARNS() << "Multiple inner XML elements (single expected)"
<< LL_ENDL;
return false;
}
if (childp->hasName("string"))
{
target.assign(LLStringFn::xml_decode(childp->getTextContents()));
return true;
}
if (childp->hasName("int") || childp->hasName("i4"))
{
target.assign(std::stoi(childp->getTextContents()));
return true;
}
if (childp->hasName("double"))
{
target.assign(std::stod(childp->getTextContents()));
return true;
}
if (childp->hasName("boolean"))
{
target.assign(std::stoi(childp->getTextContents()) != 0);
return true;
}
if (childp->hasName("dateTime.iso8601"))
{
target.assign(LLSD::Date(childp->getTextContents()));
return true;
}
if (childp->hasName("base64"))
{
std::string decoded =
LLBase64::decodeAsString(inner->getTextContents());
size_t size = decoded.size();
LLSD::Binary binary(size);
if (size)
{
memcpy((void*)binary.data(), (void*)decoded.data(), size);
}
target.assign(binary);
return true;
}
if (childp->hasName("array"))
{
if (!childp->parseXmlRpcArrayValue(target))
{
target.clear();
return false;
}
return true;
}
if (childp->hasName("struct"))
{
if (!childp->parseXmlRpcStructValue(target))
{
target.clear();
return false;
}
return true;
}
LL_WARNS() << "Unknown inner XML element (known value type expected)"
<< LL_ENDL;
// Value with unknown type qualifier is treated as string
target.assign(LLStringFn::xml_decode(childp->getTextContents()));
return true;
}

View File

@ -50,6 +50,7 @@ class LLVector3d;
class LLQuaternion;
class LLColor4;
class LLColor4U;
class LLSD;
struct CompareAttributes
@ -284,12 +285,18 @@ public:
void setAttributes(ValueType type, U32 precision, Encoding encoding, U32 length);
// void appendValue(const std::string& value); // Unused
bool fromXMLRPCValue(LLSD& target);
// Unit Testing
void createUnitTest(S32 max_num_children);
bool performUnitTest(std::string &error_buffer);
protected:
bool removeChild(LLXMLNode* child);
bool isFullyDefault();
bool parseXmlRpcArrayValue(LLSD& target);
bool parseXmlRpcStructValue(LLSD& target);
public:
std::string mID; // The ID attribute of this node
@ -328,8 +335,6 @@ protected:
static const char *skipNonWhitespace(const char *str);
static const char *parseInteger(const char *str, U64 *dest, bool *is_negative, U32 precision, Encoding encoding);
static const char *parseFloat(const char *str, F64 *dest, U32 precision, Encoding encoding);
bool isFullyDefault();
};
#endif // LL_LLXMLNODE

View File

@ -68,17 +68,13 @@ class LLXMLRPCTransaction::Handler : public LLCore::HttpHandler
{
public:
Handler(LLCore::HttpRequest::ptr_t &request, LLXMLRPCTransaction::Impl *impl);
virtual ~Handler();
virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response);
void onCompleted(LLCore::HttpHandle handle,
LLCore::HttpResponse* response) override;
typedef std::shared_ptr<LLXMLRPCTransaction::Handler> ptr_t;
private:
bool parseResponse(LLXMLNodePtr root);
bool parseValue(LLSD& target, LLXMLNodePtr source);
LLXMLRPCTransaction::Impl *mImpl;
LLCore::HttpRequest::ptr_t mRequest;
};
@ -104,6 +100,8 @@ public:
std::string mResponseText;
LLSD mResponseData;
bool mHasResponse;
bool mResponseParsed;
std::string mCertStore;
LLSD mErrorCertData;
@ -120,6 +118,10 @@ public:
void setStatus(EStatus code, const std::string& message = "", const std::string& uri = "");
void setHttpStatus(const LLCore::HttpStatus &status);
private:
bool parseResponse(LLXMLNodePtr root);
bool parseValue(LLSD& target, LLXMLNodePtr source);
};
LLXMLRPCTransaction::Handler::Handler(LLCore::HttpRequest::ptr_t &request,
@ -129,10 +131,6 @@ LLXMLRPCTransaction::Handler::Handler(LLCore::HttpRequest::ptr_t &request,
{
}
LLXMLRPCTransaction::Handler::~Handler()
{
}
void LLXMLRPCTransaction::Handler::onCompleted(LLCore::HttpHandle handle,
LLCore::HttpResponse * response)
{
@ -159,7 +157,6 @@ void LLXMLRPCTransaction::Handler::onCompleted(LLCore::HttpHandle handle,
return;
}
mImpl->setStatus(LLXMLRPCTransaction::StatusComplete);
mImpl->mTransferStats = response->getTransferStats();
// The contents of a buffer array are potentially noncontiguous, so we
@ -169,88 +166,12 @@ void LLXMLRPCTransaction::Handler::onCompleted(LLCore::HttpHandle handle,
body->read(0, mImpl->mResponseText.data(), body->size());
LLXMLNodePtr root;
if (!LLXMLNode::parseBuffer(mImpl->mResponseText.data(), body->size(), root, nullptr))
{
LL_WARNS() << "Failed parsing XML response; request URI: " << mImpl->mURI << LL_ENDL;
return;
}
if (!parseResponse(root))
return;
LL_INFOS() << "XML response parsed successfully; request URI: " << mImpl->mURI << LL_ENDL;
}
struct XMLTreeNode final : public LLSD::TreeNode
{
XMLTreeNode(const LLXMLNodePtr impl)
: mImpl(impl)
, mFirstChild(impl ? create(impl->getFirstChild()) : nullptr)
, mNextSibling(impl ? create(impl->getNextSibling()) : nullptr)
{
}
static XMLTreeNode* create(LLXMLNodePtr node) { return node ? new XMLTreeNode(node) : nullptr; }
virtual bool hasName(const LLSD::String& name) const override { return mImpl && mImpl->hasName(name); }
virtual LLSD::String getTextContents() const override { return mImpl ? mImpl->getTextContents() : LLStringUtil::null; }
virtual TreeNode* getFirstChild() const override { return mFirstChild.get(); }
virtual TreeNode* getNextSibling() const override { return mNextSibling.get(); }
private:
const LLXMLNodePtr mImpl;
const std::shared_ptr<XMLTreeNode> mFirstChild;
const std::shared_ptr<XMLTreeNode> mNextSibling;
};
bool LLXMLRPCTransaction::Handler::parseResponse(LLXMLNodePtr root)
{
// We have alreasy checked in LLXMLNode::parseBuffer()
// that root contains exactly one child
if (!root->hasName("methodResponse"))
{
LL_WARNS() << "Invalid root element in XML response; request URI: " << mImpl->mURI << LL_ENDL;
return false;
}
LLXMLNodePtr first = root->getFirstChild();
LLXMLNodePtr second = first->getFirstChild();
if (!first->getNextSibling() && second && !second->getNextSibling())
{
if (first->hasName("fault"))
{
LLSD fault;
if (parseValue(fault, second) &&
fault.isMap() && fault.has("faultCode") && fault.has("faultString"))
{
LL_WARNS() << "Request failed;"
<< " faultCode: '" << fault.get("faultCode").asString() << "',"
<< " faultString: '" << fault.get("faultString").asString() << "',"
<< " request URI: " << mImpl->mURI << LL_ENDL;
return false;
}
}
else if (first->hasName("params") &&
second->hasName("param") && !second->getNextSibling())
{
LLXMLNodePtr third = second->getFirstChild();
if (third && !third->getNextSibling() && parseValue(mImpl->mResponseData, third))
{
return true;
}
}
}
LL_WARNS() << "Invalid response format; request URI: " << mImpl->mURI << LL_ENDL;
return false;
}
bool LLXMLRPCTransaction::Handler::parseValue(LLSD& target, LLXMLNodePtr source)
{
XMLTreeNode tn(source);
return target.fromXMLRPCValue(&tn);
// We do not do the parsing in the HTTP coroutine, since it could exhaust
// the coroutine stack in extreme cases. Instead, we flag the data buffer
// as ready, and let mImpl decode it in its process() method, on the main
// coroutine. HB
mImpl->mHasResponse = true;
mImpl->setStatus(LLXMLRPCTransaction::StatusComplete);
}
//=========================================================================
@ -265,6 +186,8 @@ LLXMLRPCTransaction::Impl::Impl
: mHttpRequest()
, mStatus(LLXMLRPCTransaction::StatusNotStarted)
, mURI(uri)
, mHasResponse(false)
, mResponseParsed(false)
{
LLCore::HttpOptions::ptr_t httpOpts;
LLCore::HttpHeaders::ptr_t httpHeaders;
@ -327,6 +250,57 @@ LLXMLRPCTransaction::Impl::Impl
mURI, body.get(), httpOpts, httpHeaders, mHandler);
}
bool LLXMLRPCTransaction::Impl::parseResponse(LLXMLNodePtr root)
{
// We have already checked in LLXMLNode::parseBuffer() that root contains
// exactly one child.
if (!root->hasName("methodResponse"))
{
LL_WARNS() << "Invalid root element in XML response; request URI: "
<< mURI << LL_ENDL;
return false;
}
LLXMLNodePtr first = root->getFirstChild();
LLXMLNodePtr second = first->getFirstChild();
if (first && !first->getNextSibling() && second &&
!second->getNextSibling())
{
if (first->hasName("fault"))
{
LLSD fault;
if (parseValue(fault, second) && fault.isMap() &&
fault.has("faultCode") && fault.has("faultString"))
{
LL_WARNS() << "Request failed. faultCode: '"
<< fault.get("faultCode").asString()
<< "', faultString: '"
<< fault.get("faultString").asString()
<< "', request URI: " << mURI << LL_ENDL;
return false;
}
}
else if (first->hasName("params") &&
second->hasName("param") && !second->getNextSibling())
{
LLXMLNodePtr third = second->getFirstChild();
if (third && !third->getNextSibling() &&
parseValue(mResponseData, third))
{
return true;
}
}
}
LL_WARNS() << "Invalid response format; request URI: " << mURI << LL_ENDL;
return false;
}
bool LLXMLRPCTransaction::Impl::parseValue(LLSD& target, LLXMLNodePtr src)
{
return src->fromXMLRPCValue(target);
}
bool LLXMLRPCTransaction::Impl::process()
{
if (!mPostH || !mHttpRequest)
@ -335,6 +309,29 @@ bool LLXMLRPCTransaction::Impl::process()
return true; //failed, quit.
}
// Parse the response when we have one and it has not yet been parsed. HB
if (mHasResponse && !mResponseParsed)
{
LLXMLNodePtr root;
if (!LLXMLNode::parseBuffer(mResponseText.data(), mResponseText.size(),
root, nullptr))
{
LL_WARNS() << "Failed parsing XML in response; request URI: "
<< mURI << LL_ENDL;
}
else if (parseResponse(root))
{
LL_INFOS() << "XMLRPC response parsed successfully; request URI: "
<< mURI << LL_ENDL;
}
else
{
LL_WARNS() << "XMLRPC response parsing failed; request URI: "
<< mURI << LL_ENDL;
}
mResponseParsed = true;
}
switch (mStatus)
{
case LLXMLRPCTransaction::StatusComplete: