Adding more fixes for DoS-able security issues. Adding unit tests for the same.

This commit is contained in:
Jan Dupej 2024-09-27 11:34:29 +02:00
Родитель a0a1c7f031
Коммит d3e740c2b3
28 изменённых файлов: 848 добавлений и 55 удалений

Просмотреть файл

@ -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;
}

226
cpp/test/core/security.cpp Normal file
Просмотреть файл

@ -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;
}

6
cpp/test/core/security.h Normal file
Просмотреть файл

@ -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>