Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for DoS vulnerabilities in C++ #1210

Merged
merged 7 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,53 @@ tag versions. The Bond compiler (`gbc`) and
different versioning scheme, following the Haskell community's
[package versioning policy](https://wiki.haskell.org/Package_versioning_policy).

## 13.0.1: 2024-09-30 ##

* IDL core version: 3.0
* C++ version: 13.0.1
* C# NuGet version: 13.0.1
* Java version: 13.0.1
* `gbc` & compiler library: 0.13.0.0

### Java ###

* There were no Java changes in this release.

### C++ ###

* `InputBuffer` throws a `StreamException` when trying to skip beyond the
end of the stream. This mitigates a CPU DoS vulnerability.
* Deserialization from JSON payloads will no longer process very deeply
nested structures. Instead, a `bond::CoreException` will be thrown in
order to protect against stack overflows. The depth limit may be changed
by calling the function `bond::SetDeserializeMaxDepth(uint32_t)`.
* **Breaking change**: Protocols must now implement `CanReadArray` method and
Buffers must implement `CanRead` method. These are used to perform checks that
mitigate memory allocation vulnerabilities.
* **Breaking change**: Custom containers must implement `reset_list` and `list_insert`.
Standard implementations are provided. This API is used to incrementally fill
containers of complex types when preallocation may be unsafe. Expected container
size is provided in `reset_list`, where client code can perform sanity checks before
any memory is allocated by Bond.
* `bond::CoreException` is thrown when the payload has a greater declared size
than the backing buffer.
* **Known issue**: Debug builds with MSVC 14.0 (Visual Studio 2015) may fail at
runtime if custom allocators for containers are used. Newer MSVC versions and
other compilers are not affected, neither are Release builds with MSVC 14.0. This
can be worked around by using newer MSVC version or building in Release configuration.

### C# ###

* There were no C# changes in this release.

## 13.0 ##

This version was allocated but never released.

## 12.0 ##

This version was allocated but never released.

## 11.0.1: 2024-06-26 ##

* IDL core version: 3.0
Expand Down
5 changes: 5 additions & 0 deletions cpp/inc/bond/core/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@
#define BOND_CONSTEXPR_OR_CONST BOOST_CONSTEXPR_OR_CONST
#define BOND_STATIC_CONSTEXPR BOOST_STATIC_CONSTEXPR

#if defined(BOOST_IF_CONSTEXPR)
#define BOND_IF_CONSTEXPR BOOST_IF_CONSTEXPR
#else
#define BOND_IF_CONSTEXPR if
#endif

#define BOND_LIB_TYPE_HEADER 1
#define BOND_LIB_TYPE_STATIC 2
Expand Down
6 changes: 6 additions & 0 deletions cpp/inc/bond/core/container_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ uint32_t container_size(const T& container);
template <typename T>
void resize_list(T& list, uint32_t size);

template <typename T>
void reset_list(T& list, uint32_t size_hint);

template <typename T, typename E>
void insert_list(T& list, const E& item);

template <typename T, typename E, typename F>
void modify_element(T& list, E& element, F deserialize);

Expand Down
127 changes: 94 additions & 33 deletions cpp/inc/bond/core/detail/typeid_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,47 @@
namespace bond
{

namespace detail
{
// The following control which containers will be preallocated when deserializing
// and which require incrementally adding items so that a large size in a payload
// doesn't cause a large memory allocation.
template <typename TContainer, typename TElement, typename Enable = void>
struct is_deserialize_direct
: std::false_type {};

template <typename TContainer, typename TElement>
struct is_deserialize_direct<TContainer, TElement,
typename boost::enable_if_c<require_modify_element<TContainer>::value
&& is_element_matching<TElement, TContainer>::value>::type>
: std::true_type {};

template <typename TContainer, typename TElement>
struct is_deserialize_direct<TContainer, TElement,
typename boost::enable_if_c<is_list_container<TContainer>::value
&& !is_nullable<TContainer>::value
&& is_basic_type<typename remove_bonded_value<TElement>::type>::value
&& !require_modify_element<TContainer>::value
&& is_element_matching<TElement, TContainer>::value>::type>
: std::true_type {};

template <typename TContainer, typename TElement, typename Enable = void>
struct is_deserialize_incremental
: std::false_type {};

template <typename TElement>
struct is_deserialize_incremental<nullable<TElement>, TElement>
: std::false_type {};

template <typename TContainer, typename TElement>
struct is_deserialize_incremental<TContainer, TElement,
typename boost::enable_if_c<is_list_container<TContainer>::value
&& !is_basic_type<typename remove_bonded_value<TElement>::type>::value
&& is_element_matching<TElement, TContainer>::value
&& !require_modify_element<TContainer>::value>::type>
: std::true_type {};
};

template <typename Protocols, typename X, typename Key, typename T>
typename boost::enable_if<is_map_key_matching<Key, X> >::type
inline DeserializeMapElements(X& var, const Key& key, const T& element, uint32_t size);
Expand All @@ -25,14 +66,17 @@ template <typename Protocols, typename Transform, typename Key, typename T>
inline void DeserializeMapElements(const Transform& transform, const Key& key, const T& element, uint32_t size);

template <typename Protocols, typename X, typename T>
typename boost::enable_if_c<is_list_container<X>::value
&& is_element_matching<T, X>::value>::type
typename boost::enable_if_c<detail::is_deserialize_direct<X, T>::value>::type
inline DeserializeElements(X& var, const T& element, uint32_t size);

template <typename Protocols, typename X, typename T>
typename boost::enable_if<is_matching<T, X> >::type
typename boost::enable_if_c<is_matching<T, X>::value>::type
inline DeserializeElements(nullable<X>& var, const T& element, uint32_t size);

template <typename Protocols, typename X, typename T>
typename boost::enable_if_c<detail::is_deserialize_incremental<X, T>::value>::type
inline DeserializeElements(X& var, const T& element, uint32_t size);

template <typename Protocols, typename Reader>
inline void DeserializeElements(blob& var, const value<blob::value_type, Reader&>& element, uint32_t size);

Expand All @@ -42,11 +86,11 @@ typename boost::enable_if_c<is_set_container<X>::value
inline DeserializeElements(X& var, const T& element, uint32_t size);

template <typename Protocols, typename X, typename T>
typename boost::disable_if<is_element_matching<T, X> >::type
typename boost::enable_if_c<!is_element_matching<T, X>::value >::type
inline DeserializeElements(X&, const T& element, uint32_t size);

template <typename Protocols, typename Transform, typename T>
inline void DeserializeElements(const Transform& transform, const T& element, uint32_t size);
void inline DeserializeElements(const Transform& transform, const T& element, uint32_t size);

namespace detail
{
Expand Down Expand Up @@ -157,6 +201,23 @@ inline bool BasicTypeField(uint16_t id, const Metadata& metadata, BondDataType t
}


template <typename Reader, typename T = uint8_t>
inline void CheckInputData(Reader& input, uint32_t size)
{
if (!input.template CanReadArray<T>(size))
{
OutOfBoundObjectSizeException();
}
}

template <typename Protocols, typename T, typename E, typename Reader>
inline void DeserializeElementsChecked(T& var, Reader& input, uint32_t size)
{
CheckInputData<Reader, E>(input, size);
return DeserializeElements<Protocols>(var, value<E, Reader&>(input, false), size);
}


template <typename Protocols, typename T, typename Reader>
inline void BasicTypeContainer(T& var, BondDataType type, Reader& input, uint32_t size)
{
Expand All @@ -165,43 +226,43 @@ inline void BasicTypeContainer(T& var, BondDataType type, Reader& input, uint32_
switch (type)
{
case bond::BT_BOOL:
return DeserializeElements<Protocols>(var, value<bool, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, bool, Reader>(var, input, size);

case bond::BT_UINT8:
return DeserializeElements<Protocols>(var, value<uint8_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, uint8_t, Reader>(var, input, size);

case bond::BT_UINT16:
return DeserializeElements<Protocols>(var, value<uint16_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, uint16_t, Reader>(var, input, size);

case bond::BT_UINT32:
return DeserializeElements<Protocols>(var, value<uint32_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, uint32_t, Reader>(var, input, size);

case bond::BT_UINT64:
return DeserializeElements<Protocols>(var, value<uint64_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, uint64_t, Reader>(var, input, size);

case bond::BT_FLOAT:
return DeserializeElements<Protocols>(var, value<float, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, float, Reader>(var, input, size);

case bond::BT_DOUBLE:
return DeserializeElements<Protocols>(var, value<double, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, double, Reader>(var, input, size);

case bond::BT_STRING:
return DeserializeElements<Protocols>(var, value<std::string, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, std::string, Reader>(var, input, size);

case bond::BT_WSTRING:
return DeserializeElements<Protocols>(var, value<std::wstring, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, std::wstring, Reader>(var, input, size);

case bond::BT_INT8:
return DeserializeElements<Protocols>(var, value<int8_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, int8_t, Reader>(var, input, size);

case bond::BT_INT16:
return DeserializeElements<Protocols>(var, value<int16_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, int16_t, Reader>(var, input, size);

case bond::BT_INT32:
return DeserializeElements<Protocols>(var, value<int32_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, int32_t, Reader>(var, input, size);

case bond::BT_INT64:
return DeserializeElements<Protocols>(var, value<int64_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, int64_t, Reader>(var, input, size);

default:
BOOST_ASSERT(false);
Expand Down Expand Up @@ -232,7 +293,7 @@ inline MatchingTypeContainer(T& var, BondDataType type, Reader& input, uint32_t
{
if (type == get_type_id<typename element_type<T>::type>::value)
{
DeserializeElements<Protocols>(var, value<typename element_type<T>::type, Reader&>(input, false), size);
DeserializeElementsChecked<Protocols, T, typename element_type<T>::type, Reader>(var, input, size);
}
else
{
Expand All @@ -250,7 +311,7 @@ inline MatchingTypeContainer(T& var, BondDataType type, Reader& input, uint32_t
switch (type)
{
case bond::BT_BOOL:
return DeserializeElements<Protocols>(var, value<bool, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, bool, Reader>(var, input, size);

default:
BOOST_ASSERT(!IsMatching<typename element_type<T>::type>(type));
Expand All @@ -268,7 +329,7 @@ inline MatchingTypeContainer(T& var, BondDataType type, Reader& input, uint32_t
switch (type)
{
case bond::BT_STRING:
return DeserializeElements<Protocols>(var, value<std::string, Reader&>(input, false), size);
return DeserializeElementsChecked < Protocols, T, std::string, Reader > (var, input, size);

default:
BOOST_ASSERT(!IsMatching<typename element_type<T>::type>(type));
Expand All @@ -286,7 +347,7 @@ inline MatchingTypeContainer(T& var, BondDataType type, Reader& input, uint32_t
switch (type)
{
case bond::BT_WSTRING:
return DeserializeElements<Protocols>(var, value<std::wstring, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, std::wstring, Reader>(var, input, size);

default:
BOOST_ASSERT(!IsMatching<typename element_type<T>::type>(type));
Expand All @@ -304,10 +365,10 @@ inline MatchingTypeContainer(T& var, BondDataType type, Reader& input, uint32_t
switch (type)
{
case bond::BT_FLOAT:
return DeserializeElements<Protocols>(var, value<float, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, float, Reader>(var, input, size);

case bond::BT_DOUBLE:
return DeserializeElements<Protocols>(var, value<double, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, double, Reader>(var, input, size);

default:
BOOST_ASSERT(!IsMatching<typename element_type<T>::type>(type));
Expand All @@ -325,16 +386,16 @@ inline MatchingTypeContainer(T& var, BondDataType type, Reader& input, uint32_t
switch (type)
{
case bond::BT_UINT8:
return DeserializeElements<Protocols>(var, value<uint8_t, Reader&>(input, false), size);

return DeserializeElementsChecked<Protocols, T, uint8_t, Reader>(var, input, size);
case bond::BT_UINT16:
return DeserializeElements<Protocols>(var, value<uint16_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, uint16_t, Reader>(var, input, size);

case bond::BT_UINT32:
return DeserializeElements<Protocols>(var, value<uint32_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, uint32_t, Reader>(var, input, size);

case bond::BT_UINT64:
return DeserializeElements<Protocols>(var, value<uint64_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, uint64_t, Reader>(var, input, size);

default:
BOOST_ASSERT(!IsMatching<typename element_type<T>::type>(type));
Expand All @@ -352,16 +413,16 @@ inline MatchingTypeContainer(T& var, BondDataType type, Reader& input, uint32_t
switch (type)
{
case bond::BT_INT8:
return DeserializeElements<Protocols>(var, value<int8_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, int8_t, Reader>(var, input, size);

case bond::BT_INT16:
return DeserializeElements<Protocols>(var, value<int16_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, int16_t, Reader>(var, input, size);

case bond::BT_INT32:
return DeserializeElements<Protocols>(var, value<int32_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, int32_t, Reader>(var, input, size);

case bond::BT_INT64:
return DeserializeElements<Protocols>(var, value<int64_t, Reader&>(input, false), size);
return DeserializeElementsChecked<Protocols, T, int64_t, Reader>(var, input, size);

default:
BOOST_ASSERT(!IsMatching<typename element_type<T>::type>(type));
Expand Down
12 changes: 12 additions & 0 deletions cpp/inc/bond/core/exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ struct CoreException
"Max recursion depth exceeded");
}

[[noreturn]] inline void OutOfBoundObjectSizeException()
{
BOND_THROW(CoreException,
"Payload had an element size larger than the input buffer");
}

[[noreturn]] inline void OutOfBoundStringSizeException()
{
BOND_THROW(CoreException,
"Payload-specified string length exceeds the input buffer size");
}

namespace detail
{
template <typename Key>
Expand Down
22 changes: 22 additions & 0 deletions cpp/inc/bond/core/nullable.h
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,19 @@ void resize_list(nullable<T>& value, uint32_t size)
}
}

// reset_list
template <typename T>
void reset_list(nullable<T>& value, uint32_t /*size_hint*/)
{
value.reset();
}

// insert_list
template <typename T, typename E>
void insert_list(nullable<T>& value, const E& item)
{
return value.set(item);
}

template <typename T> struct
element_type<nullable<T> >
Expand Down Expand Up @@ -633,5 +646,14 @@ template <typename T> struct
is_list_container<nullable<T> >
: std::true_type {};

namespace detail
{
template <typename T> struct
is_nullable
: std::false_type {};

template <typename T> struct
is_nullable<bond::nullable<T> >
: std::true_type {};
};
} // namespace bond
2 changes: 2 additions & 0 deletions cpp/inc/bond/core/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,8 @@ class DOMParser
template <typename Schema, typename Transform>
bool Apply(const Transform& transform, const Schema& schema)
{
detail::RecursionGuard guard;

if (!_base) _input.Parse();
return this->Read(schema, transform);
}
Expand Down
Loading
Loading