Adding more fixes for DoS-able security issues. Adding unit tests for the same.
This commit is contained in:
Родитель
a0a1c7f031
Коммит
d3e740c2b3
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -11,6 +11,43 @@ 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 ##
|
||||
|
||||
### 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.
|
||||
|
||||
### 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
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
#define BOND_CONSTEXPR BOOST_CONSTEXPR
|
||||
#define BOND_CONSTEXPR_OR_CONST BOOST_CONSTEXPR_OR_CONST
|
||||
#define BOND_STATIC_CONSTEXPR BOOST_STATIC_CONSTEXPR
|
||||
|
||||
#define BOND_IF_CONSTEXPR BOOST_IF_CONSTEXPR
|
||||
|
||||
#define BOND_LIB_TYPE_HEADER 1
|
||||
#define BOND_LIB_TYPE_STATIC 2
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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.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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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> >
|
||||
|
@ -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
|
||||
|
|
|
@ -448,6 +448,8 @@ public:
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -165,6 +165,12 @@ namespace detail
|
|||
struct SchemaReader
|
||||
{
|
||||
using Parser = StaticParser<SchemaReader&>;
|
||||
|
||||
template<typename T>
|
||||
bool CanReadArray(uint32_t /*num_elems*/) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -161,6 +161,13 @@ make_element(T& container)
|
|||
return typename element_type<T>::type(container.get_allocator());
|
||||
}
|
||||
|
||||
template <typename T, typename E>
|
||||
inline
|
||||
void insert_list(T& list, const E& item)
|
||||
{
|
||||
list.push_back(item);
|
||||
}
|
||||
|
||||
|
||||
// resize_list
|
||||
template <typename T>
|
||||
|
@ -181,6 +188,12 @@ resize_list(T& list, uint32_t size)
|
|||
list.resize(size, make_element(list));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline
|
||||
void reset_list(T& list, uint32_t/* size_hint*/)
|
||||
{
|
||||
list.clear();
|
||||
}
|
||||
|
||||
// modify_element
|
||||
template <typename A, typename F>
|
||||
|
|
|
@ -227,7 +227,7 @@ public:
|
|||
{
|
||||
Skip();
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
Reader _input;
|
||||
|
@ -675,7 +675,6 @@ inline DeserializeElement(X& var, const I& item, const T& element)
|
|||
modify_element(var, item, DeserializeImpl(element));
|
||||
}
|
||||
|
||||
|
||||
template <typename Protocols, typename X, typename I, typename T>
|
||||
typename boost::disable_if<require_modify_element<X> >::type
|
||||
inline DeserializeElement(X&, I& item, const T& element)
|
||||
|
@ -684,12 +683,12 @@ inline DeserializeElement(X&, I& item, const T& element)
|
|||
}
|
||||
|
||||
|
||||
// Read elements of a list
|
||||
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)
|
||||
{
|
||||
// In lists of basic types we can easily verify buffer size (and we have done so
|
||||
// by the time execution gets here), so it is safe to allocate the entire array.
|
||||
resize_list(var, size);
|
||||
|
||||
for (enumerator<X> items(var); items.more();)
|
||||
|
@ -698,9 +697,11 @@ 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)
|
||||
{
|
||||
// No need to guard against memory allocation attack here. Since X is nullable,
|
||||
// at most 1 element will be allocated.
|
||||
resize_list(var, size);
|
||||
|
||||
for (enumerator<nullable<X> > items(var); items.more(); --size)
|
||||
|
@ -712,6 +713,28 @@ inline DeserializeElements(nullable<X>& var, const T& element, uint32_t size)
|
|||
detail::SkipElements(element, size);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T& get_ref(T& t) noexcept
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
reset_list(var, size);
|
||||
|
||||
// Containers of structs cannot be easily checked. We resort to incrementally
|
||||
// growing the array.
|
||||
while (size--)
|
||||
{
|
||||
auto e(make_element(var));
|
||||
DeserializeElement<Protocols>(var, get_ref(e), element);
|
||||
insert_list(var, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename Protocols, typename Reader>
|
||||
inline void DeserializeElements(blob& var, const value<blob::value_type, Reader&>& element, uint32_t size)
|
||||
|
@ -738,7 +761,7 @@ 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)
|
||||
{
|
||||
detail::SkipElements(element, size);
|
||||
|
@ -806,6 +829,8 @@ inline DeserializeContainer(X& var, const T& element, Reader& input)
|
|||
case bond::BT_LIST:
|
||||
case bond::BT_STRUCT:
|
||||
{
|
||||
// Buffer check is not needed here since we do not preallocate here. Elements are deserialized
|
||||
// into a growing array unless the array is of a basic type.
|
||||
if (type == GetTypeId(element))
|
||||
{
|
||||
DeserializeElements<Protocols>(var, element, size);
|
||||
|
@ -818,6 +843,7 @@ inline DeserializeContainer(X& var, const T& element, Reader& input)
|
|||
}
|
||||
default:
|
||||
{
|
||||
// Buffer checks are performed inside as needed.
|
||||
detail::BasicTypeContainer<Protocols>(var, type, input, size);
|
||||
break;
|
||||
}
|
||||
|
@ -840,6 +866,7 @@ inline DeserializeContainer(X& var, const T& element, Reader& input)
|
|||
|
||||
if (type == GetTypeId(element))
|
||||
{
|
||||
// Buffer check is not needed here. We use a growing array for lists of nonbasic types.
|
||||
DeserializeElements<Protocols>(var, element, size);
|
||||
}
|
||||
else
|
||||
|
@ -882,6 +909,7 @@ inline DeserializeContainer(X& var, const T& element, Reader& input)
|
|||
}
|
||||
default:
|
||||
{
|
||||
// Buffer checks performed inside as needed.
|
||||
detail::MatchingTypeContainer<Protocols>(var, type, input, size);
|
||||
break;
|
||||
}
|
||||
|
@ -1082,7 +1110,6 @@ inline DeserializeMap(X& var, BondDataType keyType, const T& element, Reader& in
|
|||
input.ReadContainerEnd();
|
||||
}
|
||||
|
||||
|
||||
} // namespace bond
|
||||
|
||||
|
||||
|
|
|
@ -373,6 +373,14 @@ public:
|
|||
uint32_t length = 0;
|
||||
|
||||
Read(length);
|
||||
|
||||
constexpr uint8_t charSize = static_cast<uint8_t>(sizeof(typename detail::string_char_int_type<T>::type));
|
||||
uint32_t numStringBytes = detail::checked_multiply(length, charSize);
|
||||
if (!_input.CanRead(numStringBytes))
|
||||
{
|
||||
OutOfBoundStringSizeException();
|
||||
}
|
||||
|
||||
detail::ReadStringData(_input, value, length);
|
||||
}
|
||||
|
||||
|
@ -383,6 +391,28 @@ public:
|
|||
_input.Read(value, size);
|
||||
}
|
||||
|
||||
// Does the reader have enough input buffer left to read an array of T?
|
||||
template<typename T>
|
||||
bool CanReadArray(uint32_t num_elems)
|
||||
{
|
||||
// Non-float types have variable length encoding going down to 1 Byte.
|
||||
// Strings need 1 Byte per charcter. Wide strings handled below.
|
||||
return _input.CanRead(num_elems);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
typename boost::enable_if<typename std::is_floating_point<T>::value, bool>::type
|
||||
CanReadArray(uint32_t num_elems)
|
||||
{
|
||||
// We will need to read num_elems instances of T. This will not overflow because
|
||||
// num_elems < 2^32 and we call this only if std::is_floating_point<T>.
|
||||
uint64_t num_bytes = static_cast<uint64_t>(num_elems) * sizeof(T);
|
||||
|
||||
// Check the upper half to ensure we don't try to read more than 4 GB, the
|
||||
// Reader wouldn't be able to handle that. Then ask the Reader if it has enough
|
||||
// data left for our vector.
|
||||
return (num_bytes >> 32 == 0) && _input.CanRead(static_cast<uint32_t>(num_bytes & 0xffffffff));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Skip()
|
||||
|
|
|
@ -181,6 +181,12 @@ public:
|
|||
uint32_t length = 0;
|
||||
|
||||
ReadVariableUnsigned(_input, length);
|
||||
|
||||
constexpr uint8_t charSize = static_cast<uint8_t>(sizeof(typename detail::string_char_int_type<T>::type));
|
||||
uint32_t numStringBytes = detail::checked_multiply(length, charSize);
|
||||
if (!_input.CanRead(numStringBytes))
|
||||
OutOfBoundStringSizeException();
|
||||
|
||||
detail::ReadStringData(_input, value, length);
|
||||
}
|
||||
|
||||
|
@ -191,6 +197,28 @@ public:
|
|||
_input.Read(value, size);
|
||||
}
|
||||
|
||||
// Does the reader have enough input buffer left to read an array of T?
|
||||
template<typename T>
|
||||
bool CanReadArray(uint32_t num_elems)
|
||||
{
|
||||
// We will need to read num_elems instances of T. This will not overflow because
|
||||
// num_elems < 2^32 and we call this only for primitive types, so sizeof(T) <= 8.
|
||||
uint64_t num_bytes = static_cast<uint64_t>(num_elems) * sizeof(T);
|
||||
return (num_bytes >> 32 == 0) && _input.CanRead(num_bytes & 0xffffffff);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool CanReadArray<std::string>(uint32_t num_elems)
|
||||
{
|
||||
return _input.CanRead(num_elems);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool CanReadArray<std::wstring>(uint32_t num_elems)
|
||||
{
|
||||
return _input.CanRead(num_elems);
|
||||
}
|
||||
|
||||
void ReadStructBegin()
|
||||
{}
|
||||
|
||||
|
|
|
@ -193,6 +193,12 @@ public:
|
|||
value.assign(buffer, size);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool CanReadArray(uint32_t /*num_elems*/) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Skip()
|
||||
{}
|
||||
|
|
|
@ -165,6 +165,14 @@ public:
|
|||
uint32_t length = 0;
|
||||
|
||||
ReadSize(length);
|
||||
|
||||
constexpr uint8_t charSize = static_cast<uint8_t>(sizeof(typename detail::string_char_int_type<T>::type));
|
||||
uint32_t numStringBytes = detail::checked_multiply(length, charSize);
|
||||
if (!_input.CanRead(numStringBytes))
|
||||
{
|
||||
OutOfBoundStringSizeException();
|
||||
}
|
||||
|
||||
detail::ReadStringData(_input, var, length);
|
||||
}
|
||||
|
||||
|
@ -175,6 +183,63 @@ public:
|
|||
_input.Read(var, size);
|
||||
}
|
||||
|
||||
// Does the reader have enough input buffer left to read an array of T?
|
||||
template<typename T>
|
||||
bool CanReadArray(uint32_t num_elems)
|
||||
{
|
||||
// We will need to read num_elems instances of T. This will not overflow because
|
||||
// num_elems < 2^32 and we call this only for primitive types, so sizeof(T) <= 8.
|
||||
uint64_t num_bytes = static_cast<uint64_t>(num_elems) * sizeof(T);
|
||||
|
||||
// Check if num_bytes is 32-bit as the Reader cannot grab more than that
|
||||
return (num_bytes >> 32 == 0) && _input.CanRead(num_bytes & 0xffffffff);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool CanReadArray<bool>(uint32_t num_elems)
|
||||
{
|
||||
// booleans are encoded as 1 Byte
|
||||
return _input.CanRead(num_elems);
|
||||
}
|
||||
|
||||
template<>
|
||||
bool CanReadArray<std::string>(uint32_t num_elems)
|
||||
{
|
||||
// This is a compile-time compare. In C++17 this problem does not exist.
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4127)
|
||||
#endif
|
||||
BOND_IF_CONSTEXPR(version == v1)
|
||||
{
|
||||
// In v1, strings encode their length as uin32 in 4 Bytes, so we multiply num_elems.
|
||||
return _input.CanRead(detail::checked_multiply(num_elems, 4));
|
||||
}
|
||||
else
|
||||
{
|
||||
// In v2, strings use variable-length encoded integers to specify length, 1 Byte each
|
||||
// ix their minumum length each.
|
||||
return _input.CanRead(num_elems);
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
}
|
||||
|
||||
template<>
|
||||
bool CanReadArray<std::wstring>(uint32_t num_elems)
|
||||
{
|
||||
// This is a compile-time compare. In C++17 this problem does not exist.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4127)
|
||||
|
||||
BOND_IF_CONSTEXPR (version == v1)
|
||||
return _input.CanRead(detail::checked_multiply(num_elems, 4));
|
||||
else
|
||||
return _input.CanRead(num_elems);
|
||||
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
// Skip for basic types
|
||||
template <typename T>
|
||||
|
|
|
@ -95,6 +95,13 @@ public:
|
|||
detail::Read(*GetValue(), var);
|
||||
}
|
||||
|
||||
// Does the reader have enough input buffer left to read an array of T?
|
||||
template<typename T>
|
||||
bool CanReadArray(uint32_t /*num_elems*/) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ReadContainerBegin(uint32_t&, T&)
|
||||
{
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <bond/core/config.h>
|
||||
|
||||
#include "simple_json_reader.h"
|
||||
#include "detail/recursionguard.h"
|
||||
|
||||
namespace bond
|
||||
{
|
||||
|
@ -49,6 +50,8 @@ SimpleJsonReader<BufferT>::FindField(uint16_t id, const Metadata& metadata, Bond
|
|||
template <typename Protocols, typename A, typename T, typename Buffer>
|
||||
inline void DeserializeContainer(std::vector<bool, A>& var, const T& /*element*/, SimpleJsonReader<Buffer>& reader)
|
||||
{
|
||||
bond::detail::RecursionGuard guard;
|
||||
|
||||
rapidjson::Value::ConstValueIterator it = reader.ArrayBegin();
|
||||
resize_list(var, reader.ArraySize());
|
||||
|
||||
|
@ -63,6 +66,8 @@ inline void DeserializeContainer(std::vector<bool, A>& var, const T& /*element*/
|
|||
template <typename Protocols, typename T, typename Buffer>
|
||||
inline void DeserializeContainer(blob& var, const T& /*element*/, SimpleJsonReader<Buffer>& reader)
|
||||
{
|
||||
bond::detail::RecursionGuard guard;
|
||||
|
||||
if (uint32_t size = reader.ArraySize())
|
||||
{
|
||||
boost::shared_ptr<char[]> buffer = boost::make_shared_noinit<char[]>(size);
|
||||
|
@ -84,6 +89,8 @@ template <typename Protocols, typename X, typename T, typename Buffer>
|
|||
inline typename boost::enable_if<is_list_container<X> >::type
|
||||
DeserializeContainer(X& var, const T& element, SimpleJsonReader<Buffer>& reader)
|
||||
{
|
||||
bond::detail::RecursionGuard guard;
|
||||
|
||||
detail::JsonTypeMatching type(get_type_id<typename element_type<X>::type>::value,
|
||||
GetTypeId(element),
|
||||
std::is_enum<typename element_type<X>::type>::value);
|
||||
|
@ -116,6 +123,8 @@ template <typename Protocols, typename X, typename T, typename Buffer>
|
|||
inline typename boost::enable_if<is_set_container<X> >::type
|
||||
DeserializeContainer(X& var, const T& element, SimpleJsonReader<Buffer>& reader)
|
||||
{
|
||||
bond::detail::RecursionGuard guard;
|
||||
|
||||
detail::JsonTypeMatching type(get_type_id<typename element_type<X>::type>::value,
|
||||
GetTypeId(element),
|
||||
std::is_enum<typename element_type<X>::type>::value);
|
||||
|
@ -139,6 +148,8 @@ template <typename Protocols, typename X, typename T, typename Buffer>
|
|||
inline typename boost::enable_if<is_map_container<X> >::type
|
||||
DeserializeMap(X& var, BondDataType keyType, const T& element, SimpleJsonReader<Buffer>& reader)
|
||||
{
|
||||
bond::detail::RecursionGuard guard;
|
||||
|
||||
detail::JsonTypeMatching key_type(
|
||||
get_type_id<typename element_type<X>::type::first_type>::value,
|
||||
keyType,
|
||||
|
|
|
@ -194,7 +194,7 @@ public:
|
|||
{
|
||||
if (size > _blob.length() - _pointer)
|
||||
{
|
||||
return;
|
||||
EofException(size);
|
||||
}
|
||||
|
||||
_pointer += size;
|
||||
|
@ -206,6 +206,11 @@ public:
|
|||
return _pointer == _blob.length();
|
||||
}
|
||||
|
||||
/// @brief Check if the stream can read at least @size bytes before encountering end of stream.
|
||||
bool CanRead(uint32_t size) const
|
||||
{
|
||||
return size <= _blob.length() - _pointer;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ReadVariableUnsigned(T& value)
|
||||
|
@ -226,7 +231,7 @@ protected:
|
|||
[[noreturn]] void EofException(uint32_t size) const
|
||||
{
|
||||
BOND_THROW(StreamException,
|
||||
"Read out of bounds: " << size << " bytes requested, offset: "
|
||||
"Read or skip out of bounds: " << size << " bytes requested, offset: "
|
||||
<< _pointer << ", length: " << _blob.length());
|
||||
}
|
||||
|
||||
|
|
|
@ -76,13 +76,14 @@ add_bond_codegen (TARGET unit_test_codegen1
|
|||
--header=\\\"container_extensibility.h\\\")
|
||||
|
||||
add_bond_codegen (TARGET unit_test_codegen2
|
||||
unit_test_core.bond
|
||||
apply_test.bond
|
||||
cmdargs.bond
|
||||
import_test1.bond
|
||||
scope_test1.bond
|
||||
scope_test2.bond
|
||||
security.bond
|
||||
unit_test_core.bond
|
||||
validation.bond
|
||||
cmdargs.bond
|
||||
OPTIONS
|
||||
--import-dir=imports
|
||||
--header=\\\"custom_protocols.h\\\")
|
||||
|
@ -147,6 +148,7 @@ add_unit_test (numeric_conversions.cpp)
|
|||
add_unit_test (pass_through.cpp)
|
||||
add_unit_test (protocol_test.cpp)
|
||||
add_unit_test (required_fields_tests.cpp)
|
||||
add_unit_test (security.cpp)
|
||||
add_unit_test (serialization_test.cpp)
|
||||
add_unit_test (set_tests.cpp)
|
||||
add_unit_test (skip_id_tests.cpp)
|
||||
|
|
|
@ -130,6 +130,23 @@ void resize_list(SimpleList<T>& list, uint32_t size)
|
|||
list.size = size;
|
||||
}
|
||||
|
||||
// reset_list
|
||||
template <typename T>
|
||||
void reset_list(SimpleList<T>& list, uint32_t size)
|
||||
{
|
||||
BOOST_ASSERT(size <= c_max_list_size);
|
||||
list.size = 0;
|
||||
}
|
||||
|
||||
// insert_list
|
||||
template <typename E>
|
||||
inline
|
||||
void insert_list(SimpleList<E>& list, const E& item)
|
||||
{
|
||||
BOOST_ASSERT(list.size < c_max_list_size);
|
||||
list.items[list.size] = item;
|
||||
list.size++;
|
||||
}
|
||||
|
||||
namespace bond
|
||||
{
|
||||
|
|
|
@ -113,6 +113,11 @@ namespace unit_test
|
|||
return _buffer.IsEof();
|
||||
}
|
||||
|
||||
bool CanRead(uint32_t size) const
|
||||
{
|
||||
return _buffer.CanRead(size);
|
||||
}
|
||||
|
||||
private:
|
||||
friend bond::blob GetCurrentBuffer(const CustomInputBuffer& input)
|
||||
{
|
||||
|
|
|
@ -225,26 +225,128 @@ TEST_CASE_BEGIN(ReaderOverCStr)
|
|||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(DeepNesting)
|
||||
TEST_CASE_BEGIN(SimpleType_DeepNestedPayloadOfArray)
|
||||
{
|
||||
const size_t nestingDepth = 10000;
|
||||
|
||||
std::string listOpens(nestingDepth, '[');
|
||||
std::string listCloses(nestingDepth, ']');
|
||||
|
||||
std::string deeplyNestedList = boost::str(
|
||||
std::string deeplyNestedJson = boost::str(
|
||||
boost::format("{\"deeplyNestedList\": %strue%s}") % listOpens % listCloses);
|
||||
|
||||
bond::SimpleJsonReader<const char*> json_reader(deeplyNestedList.c_str());
|
||||
bond::SimpleJsonReader<const char*> json_reader(deeplyNestedJson.c_str());
|
||||
|
||||
// The type here doesn't really matter. We need something with no
|
||||
// required fields, as we're really just testing that we can parse a
|
||||
// deeply nested JSON array without crashing.
|
||||
// The type here doesn't really matter. We need something with no struct
|
||||
// or container fields, as we're really just testing that RapidJSON can
|
||||
// parse a deeply nested JSON array without throwing.
|
||||
SimpleStruct to;
|
||||
bond::Deserialize(json_reader, to);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SimpleType_DeepNestedPayloadOfStruct)
|
||||
{
|
||||
const size_t nestingDepth = 10000;
|
||||
|
||||
std::string deeplyNestedJson;
|
||||
|
||||
for (int i = 0; i < nestingDepth; i++)
|
||||
{
|
||||
deeplyNestedJson += "{\"a\":";
|
||||
}
|
||||
|
||||
deeplyNestedJson += "\"some-inner-most-value\"";
|
||||
|
||||
for (int i = 0; i < nestingDepth; i++)
|
||||
{
|
||||
deeplyNestedJson += "}";
|
||||
}
|
||||
|
||||
bond::SimpleJsonReader<const char*> json_reader(deeplyNestedJson.c_str());
|
||||
|
||||
// The type here doesn't really matter. We need something with no struct
|
||||
// or container fields, as we're really just testing that RapidJSON can
|
||||
// parse a deeply nested JSON object without throwing.
|
||||
SimpleStruct to;
|
||||
bond::Deserialize(json_reader, to);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(RecursiveType_ThrowsOnTooManyNestings)
|
||||
{
|
||||
const size_t nestingLevel = 64;
|
||||
|
||||
std::string deeplyNestedJson;
|
||||
|
||||
for (int i = 0; i < nestingLevel; i++)
|
||||
{
|
||||
deeplyNestedJson += "{\"Child\":[";
|
||||
}
|
||||
|
||||
for (int i = 0; i < nestingLevel; i++)
|
||||
{
|
||||
deeplyNestedJson += "]}";
|
||||
}
|
||||
|
||||
StructWithRecursiveReference to;
|
||||
bond::SimpleJsonReader<const char*> json_reader_from_string(deeplyNestedJson.c_str());
|
||||
|
||||
// Validate deserialization throws
|
||||
UT_AssertThrows((bond::Deserialize(json_reader_from_string, to)), bond::CoreException);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SetDeserializeMaxDepth_ImpactsValidWorkloads)
|
||||
{
|
||||
NestedStruct3 from;
|
||||
InitRandom(from);
|
||||
|
||||
// Default max depth
|
||||
{
|
||||
bond::OutputBuffer output;
|
||||
bond::SimpleJsonWriter<bond::OutputBuffer> json_writer(output);
|
||||
|
||||
Serialize(from, json_writer);
|
||||
|
||||
bond::SimpleJsonReader<bond::InputBuffer> json_reader(output.GetBuffer());
|
||||
NestedStruct3 to;
|
||||
Deserialize(json_reader, to);
|
||||
UT_Equal(from, to);
|
||||
}
|
||||
|
||||
// Lower the depth to the point where a valid workload fails
|
||||
{
|
||||
bond::OutputBuffer output;
|
||||
bond::SimpleJsonWriter<bond::OutputBuffer> json_writer(output);
|
||||
|
||||
Serialize(from, json_writer);
|
||||
|
||||
bond::SetDeserializeMaxDepth(1);
|
||||
|
||||
bond::SimpleJsonReader<bond::InputBuffer> json_reader(output.GetBuffer());
|
||||
NestedStruct3 to;
|
||||
UT_AssertThrows((bond::Deserialize(json_reader, to)), bond::CoreException);
|
||||
}
|
||||
|
||||
// Put the depth back and validate the workload works again
|
||||
{
|
||||
bond::OutputBuffer output;
|
||||
bond::SimpleJsonWriter<bond::OutputBuffer> json_writer(output);
|
||||
|
||||
Serialize(from, json_writer);
|
||||
|
||||
bond::SetDeserializeMaxDepth(64);
|
||||
|
||||
bond::SimpleJsonReader<bond::InputBuffer> json_reader(output.GetBuffer());
|
||||
NestedStruct3 to;
|
||||
Deserialize(json_reader, to);
|
||||
UT_Equal(from, to);
|
||||
}
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
|
||||
void JSONTest::Initialize()
|
||||
{
|
||||
UnitTestSuite suite("Simple JSON test");
|
||||
|
@ -256,8 +358,12 @@ void JSONTest::Initialize()
|
|||
bond::SimpleJsonWriter<bond::OutputBuffer> >(suite);
|
||||
);
|
||||
|
||||
AddTestCase<TEST_ID(0x1c05), DeepNesting>(suite, "Deeply nested JSON struct");
|
||||
AddTestCase<TEST_ID(0x1c06), ReaderOverCStr>(suite, "SimpleJsonReader<const char*> specialization");
|
||||
AddTestCase<TEST_ID(0x1c05), ReaderOverCStr>(suite, "SimpleJsonReader<const char*> specialization");
|
||||
AddTestCase<TEST_ID(0x1c06), SimpleType_DeepNestedPayloadOfArray>(suite, "Simple struct can be parsed from payload with deeply nested JSON array");
|
||||
AddTestCase<TEST_ID(0x1c07), SimpleType_DeepNestedPayloadOfStruct>(suite, "Simple struct can be parsed from payload with deeply nested JSON object");
|
||||
AddTestCase<TEST_ID(0x1c08), RecursiveType_ThrowsOnTooManyNestings>(suite, "Too many nestings in recursive type results in exception");
|
||||
AddTestCase<TEST_ID(0x1c09), SetDeserializeMaxDepth_ImpactsValidWorkloads>(suite, "Test that SetDeserializeMaxDepth has an effect");
|
||||
|
||||
}
|
||||
|
||||
bool init_unit_test()
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
namespace security
|
||||
|
||||
struct Base<T>
|
||||
{
|
||||
0: T x;
|
||||
}
|
||||
|
||||
struct Struct<T1, T2, T3> : Base<T2>
|
||||
{
|
||||
0: T3 n;
|
||||
1: T1 y;
|
||||
2: vector<T2> items;
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
#include "precompiled.h"
|
||||
#include "security.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#include "security_reflection.h"
|
||||
#include "security_types.h"
|
||||
|
||||
using namespace security;
|
||||
typedef uint32_t uint;
|
||||
|
||||
TEST_CASE_BEGIN(SecurityBadAlloc_VectorPrimitive)
|
||||
{
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0xA5, 0xA5, 0x4B, 0x0D, 0x4B, 0x0D, 0x00, 0x41,
|
||||
0x4B, 0xE7, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE };
|
||||
|
||||
// create our object based on our fuzzed bond types file
|
||||
Struct<std::string, double, Base<std::wstring> > obj;
|
||||
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::CoreException);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityBadAlloc_VectorStruct)
|
||||
{
|
||||
// The payload specifies a lot of items in Struct::items but encodes only one.
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x0A, 0x00, 0x01, 0x05, 0x03, 0x25, 0x04, 0x4B,
|
||||
0x0A, 0xFF, 0xFF, 0xFF, 0x0F, 0x05, 0x7F, 0x00,
|
||||
0x00 };
|
||||
|
||||
Struct<uint, Base<uint>, uint> obj;
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
// We will reach EOF while reading the second item in Struct::items, expect a throw.
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::StreamException);
|
||||
|
||||
// We must not allocate more fields than are encoded + 1.
|
||||
UT_AssertIsTrue(obj.items.size() <= 2);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityBadAlloc_MapPrimitive)
|
||||
{
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x0d, 0x10, 0x10, 0x7f, 0x02, 0xd0, 0x0f, 0x00 };
|
||||
|
||||
Base<map<int, int>> obj;
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::StreamException);
|
||||
|
||||
// We must not allocate more fields than are encoded + 1.
|
||||
UT_AssertIsTrue(obj.x.size() <= 2);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityBadAlloc_SetPrimitive)
|
||||
{
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x0c, 0x10, 0x7f, 0x02, 0x00 };
|
||||
|
||||
Base<set<int>> obj;
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::CoreException);
|
||||
|
||||
// We must not allocate more fields than are encoded + 1.
|
||||
UT_AssertIsTrue(obj.x.size() <= 2);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityBadAlloc_ListPrimitive)
|
||||
{
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x0b, 0x07, 0x7f, 0x00, 0x00, 0x80, 0x3f, 0x00 };
|
||||
|
||||
Base<list<float>> obj;
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::CoreException);
|
||||
|
||||
// We must not allocate more fields than are encoded + 1.
|
||||
UT_AssertIsTrue(obj.x.size() <= 2);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityBadAlloc_ListStruct)
|
||||
{
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x0b, 0x0a, 0x7f, 0x10, 0x02, 0x01, 0x10, 0x01,
|
||||
0x30, 0x04, 0x00, 0x00 };
|
||||
|
||||
Base<list<Struct<int, int, int>>> obj;
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::CoreException);
|
||||
|
||||
// We must not allocate more fields than are encoded + 1.
|
||||
UT_AssertIsTrue(obj.x.size() <= 2);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityBadAlloc_MapStruct)
|
||||
{
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x0d, 0x10, 0x0a, 0x7f, 0x02, 0x07, 0x00, 0x00,
|
||||
0x80, 0x3f, 0x01, 0x07, 0x00, 0x00, 0x40, 0x40,
|
||||
0x27, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00 };
|
||||
|
||||
Base<map<int, Struct<float, float, float>>> obj;
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::StreamException);
|
||||
|
||||
// We must not allocate more fields than are encoded + 1.
|
||||
UT_AssertIsTrue(obj.x.size() <= 2);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityNullable_MustSkip)
|
||||
{
|
||||
// We put 2 items into a nullable which may have only 0 or 1. The test
|
||||
// makes sure that the remaining items are skipped and do not corrupt
|
||||
// the rest of deserialization.
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x01, 0x0b, 0x10, 0x02, 0x08, 0x0a, 0x30, 0x02,
|
||||
0x00 };
|
||||
|
||||
Struct<int, int, bond::nullable<int>> obj;
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
bond::Deserialize(reader, obj);
|
||||
|
||||
UT_AssertIsTrue(obj.x == 0);
|
||||
UT_AssertIsTrue(obj.y == 1);
|
||||
UT_AssertIsTrue(obj.n.hasvalue() && (obj.n.value() == 4 || obj.n.value() == 5));
|
||||
UT_AssertIsTrue(obj.items.size() == 0);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityNullable_InsufficientBuffer)
|
||||
{
|
||||
// Payload spcifies a nullable<int> with 127 items, while there are only
|
||||
// 4 Bytes left in the buffer.
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x01, 0x0b, 0x10, 0x7f, 0x08, 0x30, 0x02,
|
||||
0x00 };
|
||||
|
||||
Struct<int, int, bond::nullable<int>> obj;
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::CoreException);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityCpuDos_FastBinary)
|
||||
{
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x0D, 0x00, 0x96, 0x08, 0x0E, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF };
|
||||
|
||||
// create our object based on our fuzzed bond types file
|
||||
Struct<std::string, double, Base<std::wstring> > obj;
|
||||
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::FastBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::StreamException);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
TEST_CASE_BEGIN(SecurityCpuDos_CompactBinary)
|
||||
{
|
||||
const unsigned char pMaliciousPayload[] = {
|
||||
0x01, 0x2D, 0x0E, 0x08, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAB,
|
||||
0x09};
|
||||
|
||||
// create our object based on our fuzzed bond types file
|
||||
Struct<std::string, double, Base<std::wstring> > obj;
|
||||
|
||||
bond::InputBuffer input_buffer(static_cast<const void*>(pMaliciousPayload), sizeof(pMaliciousPayload));
|
||||
bond::CompactBinaryReader<bond::InputBuffer> reader(input_buffer);
|
||||
|
||||
UT_AssertThrows(bond::Deserialize(reader, obj), bond::StreamException);
|
||||
}
|
||||
TEST_CASE_END
|
||||
|
||||
void SecurityTest::Initialize()
|
||||
{
|
||||
UnitTestSuite suite("Security tests");
|
||||
AddTestCase<TEST_ID(0x2501), SecurityCpuDos_FastBinary>(suite, "CpuDos_FastBinary");
|
||||
AddTestCase<TEST_ID(0x2502), SecurityCpuDos_CompactBinary>(suite, "CpuDos_CompactBinary");
|
||||
|
||||
AddTestCase<TEST_ID(0x2503), SecurityBadAlloc_VectorPrimitive>(suite, "BadAlloc_VectorPrimitive");
|
||||
AddTestCase<TEST_ID(0x2504), SecurityBadAlloc_VectorStruct>(suite, "BadAlloc_VectorStruct");
|
||||
AddTestCase<TEST_ID(0x2505), SecurityBadAlloc_MapPrimitive>(suite, "BadAlloc_MapPrimitive");
|
||||
AddTestCase<TEST_ID(0x2506), SecurityBadAlloc_MapStruct>(suite, "BadAlloc_MapStruct");
|
||||
AddTestCase<TEST_ID(0x2507), SecurityBadAlloc_SetPrimitive>(suite, "BadAlloc_SetPrimitive");
|
||||
AddTestCase<TEST_ID(0x2508), SecurityBadAlloc_ListPrimitive>(suite, "BadAlloc_ListPrimitive");
|
||||
AddTestCase<TEST_ID(0x2508), SecurityBadAlloc_ListStruct>(suite, "BadAlloc_ListStruct");
|
||||
|
||||
AddTestCase<TEST_ID(0x2509), SecurityNullable_MustSkip>(suite, "Nullable_MustSkip");
|
||||
AddTestCase<TEST_ID(0x250a), SecurityNullable_InsufficientBuffer>(suite, "Nullable_InsufficientBuffer");
|
||||
}
|
||||
|
||||
|
||||
bool init_unit_test()
|
||||
{
|
||||
SecurityTest::Initialize();
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
class SecurityTest
|
||||
{
|
||||
public:
|
||||
static void Initialize();
|
||||
};
|
|
@ -29,7 +29,6 @@ struct SimpleOptionalsView view_of SimpleOptionals
|
|||
b;
|
||||
};
|
||||
|
||||
|
||||
struct SimpleStruct
|
||||
{
|
||||
0: required_optional bool m_bool;
|
||||
|
|
|
@ -1487,6 +1487,14 @@ uint32_t container_size(const T& container);
|
|||
template <typename T>
|
||||
void resize_list(T& list, uint32_t size);
|
||||
|
||||
// Added in Bond 13. See note below.
|
||||
template <typename T>
|
||||
void reset_list(T& list, uint32_t size_hint);
|
||||
|
||||
// Added in Bond 13. See note below.
|
||||
template <typename T, typename E>
|
||||
void insert_list(T& list, const E& item);
|
||||
|
||||
template <typename T>
|
||||
void clear_set(T& set);
|
||||
|
||||
|
@ -1500,6 +1508,20 @@ template <typename M, typename K, typename T>
|
|||
T& mapped_at(M& map, const K& key);
|
||||
```
|
||||
|
||||
> **NOTE** The functions `reset_list` and `insert_list` need to be implemented
|
||||
> carefully to guard against DoS attacks through excessive memory allocation.
|
||||
> The function `reset_list` is called by Bond to prepare a container for inserting
|
||||
> deserialized elements. The application should examine `size_hint`; if this value
|
||||
> is unreasonably high, an exception should be thrown to signal error. Otherwise,
|
||||
> the client code should clear the container but __refrain from allocating__ any memory
|
||||
> for the data. This is because Bond is unable to verify that the payload actually
|
||||
> contains the declared number of items. Bond will then iteratively deserialize and
|
||||
> insert individual items into the container by calling `insert_list`. Custom
|
||||
> implementations should insert the provided item into the collection. It is a good
|
||||
> practice to check that the number of items in the container does not exceed
|
||||
> `size_hint`.
|
||||
|
||||
|
||||
Note that unlike the traits which need to be specialized in the `bond`
|
||||
namespace, these function can be overloaded in the namespace of the container
|
||||
type.
|
||||
|
@ -1651,6 +1673,20 @@ typename aliased_type<T>::type get_aliased_value(const T& value);
|
|||
|
||||
- `examples/cpp/core/time_alias`
|
||||
|
||||
Smart pointers
|
||||
--------------
|
||||
References to values are obtained with `get_ref`:
|
||||
```cpp
|
||||
template <typename T>
|
||||
inline T& get_ref(T& t);
|
||||
```
|
||||
|
||||
To use containers of smart pointers, Bond needs to make sure that an object is
|
||||
constructed before its reference is taken. In `get_ref` that object is created
|
||||
if needed.
|
||||
|
||||
- `examples/cpp/core/container_of_pointers`
|
||||
|
||||
Custom allocators
|
||||
=================
|
||||
|
||||
|
@ -1711,6 +1747,13 @@ public:
|
|||
|
||||
// Read into a memory blob
|
||||
void Read(bond::blob& blob, uint32_t size);
|
||||
|
||||
// Advance buffer pointer by size Bytes without returning the data. If size is
|
||||
// smaller than the remaining buffer size, throw a StreamException.
|
||||
void Skip(uint32_t size);
|
||||
|
||||
// Return true iff the next Read of size Bytes succeeds.
|
||||
bool CanRead(uint32_t size) const;
|
||||
};
|
||||
```
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ public:
|
|||
if (file.good())
|
||||
{
|
||||
file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
|
||||
InitLength();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -40,6 +41,7 @@ public:
|
|||
{
|
||||
file.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
|
||||
file.seekg(that.file.tellg());
|
||||
InitLength();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -71,6 +73,11 @@ public:
|
|||
blob.assign(buffer, 0, size);
|
||||
}
|
||||
|
||||
bool CanRead(uint32_t size) const
|
||||
{
|
||||
return size <= file_size - file.tellg();
|
||||
}
|
||||
|
||||
void Skip(uint32_t size)
|
||||
{
|
||||
file.seekg(size, std::ios::cur);
|
||||
|
@ -84,6 +91,25 @@ public:
|
|||
private:
|
||||
mutable std::ifstream file;
|
||||
std::string name;
|
||||
std::streampos file_size;
|
||||
|
||||
void InitLength()
|
||||
{
|
||||
const std::streampos orig_pos = file.tellg();
|
||||
|
||||
file.seekg(0, file.end);
|
||||
if (file.fail())
|
||||
{
|
||||
// The file is not seekable. Set size to maximum int to remove restrictions.
|
||||
file_size = std::numeric_limits<std::streampos>::max();
|
||||
file.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
file_size = file.tellg();
|
||||
file.seekg(orig_pos, file.beg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -101,6 +101,18 @@ namespace bond
|
|||
A alloc;
|
||||
typename List::iterator it, end;
|
||||
};
|
||||
|
||||
// element
|
||||
template<typename T>
|
||||
inline T& get_ref(std::shared_ptr<T>& t)
|
||||
{
|
||||
if (!t)
|
||||
{
|
||||
t = std::make_shared<T>();
|
||||
}
|
||||
|
||||
return *t;
|
||||
}
|
||||
}
|
||||
|
||||
#include <bond/core/bond.h>
|
||||
|
|
Загрузка…
Ссылка в новой задаче