Limit STVar recursion during deserialization (RIPD-1603):

Constructing deeply nested objects could allow an attacker to
cause a server to overflow its available stack.

We now enforce a 10-deep nesting limit, and signal an error
if we encounter objects that are nested deeper.

Acknowledgements:
Ripple thanks Guido Vranken for responsibly disclosing this
issues.

Bug Bounties and Responsible Disclosures:
We welcome reviews of the rippled codebase and urge reviewers
to responsibly disclose any issues that they may find. For
more on Ripple's Bug Bounty program, please visit
https://ripple.com/bug-bounty
This commit is contained in:
Howard Hinnant
2018-03-03 09:02:22 -05:00
committed by Nikolaos D. Bougalis
parent 9af994ceb4
commit 881cd4cfad
8 changed files with 1323 additions and 109 deletions

View File

@@ -53,7 +53,7 @@ public:
STArray (STArray&&); STArray (STArray&&);
STArray (STArray const&) = default; STArray (STArray const&) = default;
STArray (SField const& f, int n); STArray (SField const& f, int n);
STArray (SerialIter& sit, SField const& f); STArray (SerialIter& sit, SField const& f, int depth = 0);
explicit STArray (int n); explicit STArray (int n);
explicit STArray (SField const& f); explicit STArray (SField const& f);
STArray& operator= (STArray const&) = default; STArray& operator= (STArray const&) = default;

View File

@@ -281,7 +281,7 @@ public:
STObject(STObject const&) = default; STObject(STObject const&) = default;
STObject (const SOTemplate & type, SField const& name); STObject (const SOTemplate & type, SField const& name);
STObject (const SOTemplate & type, SerialIter & sit, SField const& name); STObject (const SOTemplate & type, SerialIter & sit, SField const& name);
STObject (SerialIter& sit, SField const& name); STObject (SerialIter& sit, SField const& name, int depth = 0);
STObject (SerialIter&& sit, SField const& name) STObject (SerialIter&& sit, SField const& name)
: STObject(sit, name) : STObject(sit, name)
{ {

View File

@@ -63,7 +63,7 @@ STArray::STArray (SField const& f, int n)
v_.reserve(n); v_.reserve(n);
} }
STArray::STArray (SerialIter& sit, SField const& f) STArray::STArray (SerialIter& sit, SField const& f, int depth)
: STBase(f) : STBase(f)
{ {
while (!sit.empty ()) while (!sit.empty ())
@@ -97,8 +97,7 @@ STArray::STArray (SerialIter& sit, SField const& f)
Throw<std::runtime_error> ("Non-object in array"); Throw<std::runtime_error> ("Non-object in array");
} }
v_.emplace_back(fn); v_.emplace_back(sit, fn, depth+1);
v_.back().set (sit, 1);
if (v_.back().setTypeFromSField (fn) == STObject::typeSetFail) if (v_.back().setTypeFromSField (fn) == STObject::typeSetFail)
{ {

View File

@@ -67,11 +67,13 @@ STObject::STObject (SOTemplate const& type,
setType (type); setType (type);
} }
STObject::STObject (SerialIter& sit, SField const& name) STObject::STObject (SerialIter& sit, SField const& name, int depth)
: STBase(name) : STBase(name)
, mType(nullptr) , mType(nullptr)
{ {
set(sit, 0); if (depth > 10)
Throw<std::runtime_error> ("Maximum nesting depth of STObject exceeded");
set(sit, depth);
} }
STObject& STObject&
@@ -206,7 +208,7 @@ bool STObject::set (SerialIter& sit, int depth)
} }
// Unflatten the field // Unflatten the field
v_.emplace_back(sit, fn); v_.emplace_back(sit, fn, depth+1);
// If the object type has a known SOTemplate then set it. // If the object type has a known SOTemplate then set it.
STObject* const obj = dynamic_cast <STObject*> (&(v_.back().get())); STObject* const obj = dynamic_cast <STObject*> (&(v_.back().get()));

View File

@@ -721,92 +721,102 @@ static boost::optional <STObject> parseObject (
return boost::none; return boost::none;
} }
STObject data (inName); try
for (auto const& fieldName : json.getMemberNames ())
{ {
Json::Value const& value = json [fieldName];
auto const& field = SField::getField (fieldName); STObject data (inName);
if (field == sfInvalid) for (auto const& fieldName : json.getMemberNames ())
{ {
error = unknown_field (json_name, fieldName); Json::Value const& value = json [fieldName];
auto const& field = SField::getField (fieldName);
if (field == sfInvalid)
{
error = unknown_field (json_name, fieldName);
return boost::none;
}
switch (field.fieldType)
{
// Object-style containers (which recurse).
case STI_OBJECT:
case STI_TRANSACTION:
case STI_LEDGERENTRY:
case STI_VALIDATION:
if (! value.isObject ())
{
error = not_an_object (json_name, fieldName);
return boost::none;
}
try
{
auto ret = parseObject (json_name + "." + fieldName,
value, field, depth + 1, error);
if (! ret)
return boost::none;
data.emplace_back (std::move (*ret));
}
catch (std::exception const&)
{
error = invalid_data (json_name, fieldName);
return boost::none;
}
break;
// Array-style containers (which recurse).
case STI_ARRAY:
try
{
auto array = parseArray (json_name + "." + fieldName,
value, field, depth + 1, error);
if (array == boost::none)
return boost::none;
data.emplace_back (std::move (*array));
}
catch (std::exception const&)
{
error = invalid_data (json_name, fieldName);
return boost::none;
}
break;
// Everything else (types that don't recurse).
default:
{
auto leaf =
parseLeaf (json_name, fieldName, &inName, value, error);
if (!leaf)
return boost::none;
data.emplace_back (std::move (*leaf));
}
break;
}
}
// Some inner object types have templates. Attempt to apply that.
if (data.setTypeFromSField (inName) == STObject::typeSetFail)
{
error = template_mismatch (inName);
return boost::none; return boost::none;
} }
switch (field.fieldType) return std::move (data);
{
// Object-style containers (which recurse).
case STI_OBJECT:
case STI_TRANSACTION:
case STI_LEDGERENTRY:
case STI_VALIDATION:
if (! value.isObjectOrNull ())
{
error = not_an_object (json_name, fieldName);
return boost::none;
}
try
{
auto ret = parseObject (json_name + "." + fieldName,
value, field, depth + 1, error);
if (! ret)
return boost::none;
data.emplace_back (std::move (*ret));
}
catch (std::exception const&)
{
error = invalid_data (json_name, fieldName);
return boost::none;
}
break;
// Array-style containers (which recurse).
case STI_ARRAY:
try
{
auto array = parseArray (json_name + "." + fieldName,
value, field, depth + 1, error);
if (array == boost::none)
return boost::none;
data.emplace_back (std::move (*array));
}
catch (std::exception const&)
{
error = invalid_data (json_name, fieldName);
return boost::none;
}
break;
// Everything else (types that don't recurse).
default:
{
auto leaf =
parseLeaf (json_name, fieldName, &inName, value, error);
if (!leaf)
return boost::none;
data.emplace_back (std::move (*leaf));
}
break;
}
} }
catch (std::exception const&)
// Some inner object types have templates. Attempt to apply that.
if (data.setTypeFromSField (inName) == STObject::typeSetFail)
{ {
error = template_mismatch (inName); error = invalid_data (json_name);
return boost::none; return boost::none;
} }
return std::move (data);
} }
static boost::optional <detail::STVar> parseArray ( static boost::optional <detail::STVar> parseArray (

View File

@@ -108,8 +108,10 @@ STVar::STVar (nonPresentObject_t, SField const& name)
{ {
} }
STVar::STVar (SerialIter& sit, SField const& name) STVar::STVar (SerialIter& sit, SField const& name, int depth)
{ {
if (depth > 10)
Throw<std::runtime_error> ("Maximum nesting depth of STVar exceeded");
switch (name.fieldType) switch (name.fieldType)
{ {
case STI_NOTPRESENT: construct<STBase>(name); return; case STI_NOTPRESENT: construct<STBase>(name); return;
@@ -125,8 +127,8 @@ STVar::STVar (SerialIter& sit, SField const& name)
case STI_VL: construct<STBlob>(sit, name); return; case STI_VL: construct<STBlob>(sit, name); return;
case STI_ACCOUNT: construct<STAccount>(sit, name); return; case STI_ACCOUNT: construct<STAccount>(sit, name); return;
case STI_PATHSET: construct<STPathSet>(sit, name); return; case STI_PATHSET: construct<STPathSet>(sit, name); return;
case STI_OBJECT: construct<STObject>(sit, name); return; case STI_OBJECT: construct<STObject>(sit, name, depth); return;
case STI_ARRAY: construct<STArray>(sit, name); return; case STI_ARRAY: construct<STArray>(sit, name, depth); return;
default: default:
Throw<std::runtime_error> ("Unknown object type"); Throw<std::runtime_error> ("Unknown object type");
} }

View File

@@ -67,7 +67,7 @@ public:
STVar (defaultObject_t, SField const& name); STVar (defaultObject_t, SField const& name);
STVar (nonPresentObject_t, SField const& name); STVar (nonPresentObject_t, SField const& name);
STVar (SerialIter& sit, SField const& name); STVar (SerialIter& sit, SField const& name, int depth = 0);
STBase& get() { return *p_; } STBase& get() { return *p_; }
STBase& operator*() { return get(); } STBase& operator*() { return get(); }

File diff suppressed because it is too large Load Diff