serialization: protect blob serialization from undefined behavior

There is currently no compiler protection when someone tries to
do (for example) `BLOB_SERIALIZER(std::vector<int>)`. You just
get runtime allocation errors. This has already eaten up dev time
before, so this PR adds a static assertion that the type must be
trivially copyable, as defined by the C++ standard. Types can
override this if applicable if they use `BLOB_SERIALIZER_FORCED`.
This commit is contained in:
jeffro256 2024-02-21 13:08:15 -06:00
parent 059028a30a
commit 9d101d5ea0
No known key found for this signature in database
GPG key ID: 6F79797A6E392442
4 changed files with 31 additions and 4 deletions

View file

@ -767,7 +767,7 @@ namespace std
BLOB_SERIALIZER(rct::key); BLOB_SERIALIZER(rct::key);
BLOB_SERIALIZER(rct::key64); BLOB_SERIALIZER(rct::key64);
BLOB_SERIALIZER(rct::ctkey); BLOB_SERIALIZER(rct::ctkey);
BLOB_SERIALIZER(rct::multisig_kLRki); BLOB_SERIALIZER_FORCED(rct::multisig_kLRki);
BLOB_SERIALIZER(rct::boroSig); BLOB_SERIALIZER(rct::boroSig);
VARIANT_TAG(debug_archive, rct::key, "rct::key"); VARIANT_TAG(debug_archive, rct::key, "rct::key");

View file

@ -81,7 +81,7 @@ BLOB_SERIALIZER(crypto::chacha_iv);
BLOB_SERIALIZER(crypto::hash); BLOB_SERIALIZER(crypto::hash);
BLOB_SERIALIZER(crypto::hash8); BLOB_SERIALIZER(crypto::hash8);
BLOB_SERIALIZER(crypto::public_key); BLOB_SERIALIZER(crypto::public_key);
BLOB_SERIALIZER(crypto::secret_key); BLOB_SERIALIZER_FORCED(crypto::secret_key);
BLOB_SERIALIZER(crypto::key_derivation); BLOB_SERIALIZER(crypto::key_derivation);
BLOB_SERIALIZER(crypto::key_image); BLOB_SERIALIZER(crypto::key_image);
BLOB_SERIALIZER(crypto::signature); BLOB_SERIALIZER(crypto::signature);

View file

@ -50,13 +50,16 @@
#include <boost/type_traits/integral_constant.hpp> #include <boost/type_traits/integral_constant.hpp>
#include <boost/mpl/bool.hpp> #include <boost/mpl/bool.hpp>
/*! \struct is_blob_type /*! \struct is_blob_type / is_blob_forced
* *
* \brief a descriptor for dispatching serialize * \brief descriptors for dispatching serialize: whether to take byte-wise copy/store to type
*/ */
template <class T> template <class T>
struct is_blob_type { typedef boost::false_type type; }; struct is_blob_type { typedef boost::false_type type; };
template <class T>
struct is_blob_forced: std::false_type {};
/*! \fn do_serialize(Archive &ar, T &v) /*! \fn do_serialize(Archive &ar, T &v)
* *
* \brief main function for dispatching serialization for a given pair of archive and value types * \brief main function for dispatching serialization for a given pair of archive and value types
@ -68,6 +71,8 @@ struct is_blob_type { typedef boost::false_type type; };
template <class Archive, class T> template <class Archive, class T>
inline std::enable_if_t<is_blob_type<T>::type::value, bool> do_serialize(Archive &ar, T &v) inline std::enable_if_t<is_blob_type<T>::type::value, bool> do_serialize(Archive &ar, T &v)
{ {
static_assert(std::is_trivially_copyable<T>() || is_blob_forced<T>(),
"sanity check: types that can't be trivially copied shouldn't be using the blob serializer");
ar.serialize_blob(&v, sizeof(v)); ar.serialize_blob(&v, sizeof(v));
return true; return true;
} }
@ -94,6 +99,9 @@ inline bool do_serialize(Archive &ar, bool &v)
/*! \macro BLOB_SERIALIZER /*! \macro BLOB_SERIALIZER
* *
* \brief makes the type have a blob serializer trait defined * \brief makes the type have a blob serializer trait defined
*
* In case your type is not a good candidate to be blob serialized, a static assertion may be thrown
* at compile-time.
*/ */
#define BLOB_SERIALIZER(T) \ #define BLOB_SERIALIZER(T) \
template<> \ template<> \
@ -101,6 +109,20 @@ inline bool do_serialize(Archive &ar, bool &v)
typedef boost::true_type type; \ typedef boost::true_type type; \
} }
/*! \macro BLOB_SERIALIZER_FORCED
*
* \brief makes the type have a blob serializer trait defined, even if it isn't trivially copyable
*
* Caution: do NOT use this macro for your type <T>, unless you are absolutely sure that moving raw
* bytes in/out of this type will not cause undefined behavior. Any types with managed memory
* (e.g. vector, string, etc) will segfault and/or cause memory errors if you use this macro with
* that type.
*/
#define BLOB_SERIALIZER_FORCED(T) \
BLOB_SERIALIZER(T); \
template<> \
struct is_blob_forced<T>: std::true_type {};
/*! \macro VARIANT_TAG /*! \macro VARIANT_TAG
* *
* \brief Adds the tag \tag to the \a Archive of \a Type * \brief Adds the tag \tag to the \a Archive of \a Type

View file

@ -51,6 +51,11 @@
using namespace std; using namespace std;
using namespace crypto; using namespace crypto;
static_assert(!std::is_trivially_copyable<std::vector<unsigned char>>(),
"should fail to compile when applying blob serializer");
static_assert(!std::is_trivially_copyable<std::string>(),
"should fail to compile when applying blob serializer");
struct Struct struct Struct
{ {
int32_t a; int32_t a;