Fix shared polymorphic poiner like type serialization/deserialization

This commit is contained in:
Mindaugas Vinkelis
2025-10-02 14:20:33 +03:00
parent 7ea1da0d48
commit 66d16516e2
5 changed files with 391 additions and 101 deletions

View File

@@ -23,6 +23,8 @@
#include <bitsery/ext/inheritance.h>
#include <bitsery/ext/pointer.h>
#include <bitsery/ext/std_smart_ptr.h>
#include <bitsery/traits/string.h>
#include <bitsery/traits/vector.h>
#include "serialization_test_utils.h"
#include <gmock/gmock.h>
@@ -77,6 +79,27 @@ serialize(S& s, Derived& o)
s.value1b(o.y);
}
struct DerivedSibling : virtual Base
{
uint32_t y{};
DerivedSibling() = default;
DerivedSibling(uint8_t x_, uint32_t y_)
{
x = x_;
y = y_;
}
};
template<typename S>
void
serialize(S& s, DerivedSibling& o)
{
s.ext(o, VirtualBaseClass<Base>{});
s.value4b(o.y);
}
struct MoreDerived : Derived
{
uint8_t z{};
@@ -741,6 +764,103 @@ TEST_F(SerializeExtensionStdSmartSharedPtr,
EXPECT_THAT(baseRes1.use_count(), Eq(0));
}
struct MightMatchVecLayout
{
size_t begin;
size_t end;
};
template<typename S>
void
serialize(S& s, MightMatchVecLayout& o)
{
s.template value<sizeof(size_t)>(o.begin);
s.template value<sizeof(size_t)>(o.end);
}
TEST_F(SerializeExtensionStdSmartSharedPtr,
NonPolymorphicObservedPointerMustMatchActuallyDeserializedObjectType)
{
std::shared_ptr<std::vector<uint8_t>> baseData1{ new std::vector<uint8_t>{
'a', 'b', 'c' } };
std::shared_ptr<MightMatchVecLayout> baseData2{ new MightMatchVecLayout{
1, 2 } };
auto& ser = createSerializer();
ser.ext(baseData1, StdSmartPtr{}, [](auto& ser, std::vector<uint8_t>& o) {
ser.container1b(o, 100);
});
ser.ext(baseData2, StdSmartPtr{});
// hack a buffer, so that during deserialization we would point to already
// deserialized object
sctx.buf[5] = 0x01;
std::shared_ptr<std::vector<uint8_t>> baseRes1{};
std::shared_ptr<MightMatchVecLayout> baseRes2{};
auto& des = createDeserializer();
des.ext(baseRes1, StdSmartPtr{}, [](auto& ser, std::vector<uint8_t>& o) {
ser.container1b(o, 100);
});
// if we would blindly trust the input (that the object is already
// deserialized), we would end up
// using memory of that object. In this case, we will get internal
// representation of std::vector.
// If this object is available to attacker, he'll be able to see leaked
// address of that object,
// allowing him to bypass ASLR.
des.ext(baseRes2, StdSmartPtr{});
EXPECT_THAT(baseRes2.get(), ::testing::IsNull());
auto err = des.adapter().error();
EXPECT_THAT(err, Eq(bitsery::ReaderError::InvalidPointer));
EXPECT_TRUE(isPointerContextValid());
}
TEST_F(
SerializeExtensionStdSmartSharedPtr,
PolymorphicObservedPointerMustBeInInheritanceChainOfActuallyDeserializedObjectType1)
{
std::shared_ptr<Base> baseData1{ new MoreDerived{ 3, 7, 10 } };
std::shared_ptr<Base> baseData2{};
baseData2 = baseData1;
auto& ser = createSerializer();
ser.ext(baseData1, StdSmartPtr{});
ser.ext(baseData2, StdSmartPtr{});
std::shared_ptr<Base> baseRes1{};
std::shared_ptr<Derived> baseRes2{};
auto& des = createDeserializer();
des.ext(baseRes1, StdSmartPtr{});
des.ext(baseRes2, StdSmartPtr{});
EXPECT_THAT(baseRes2.get(), ::testing::NotNull());
auto err = des.adapter().error();
EXPECT_THAT(err, Eq(bitsery::ReaderError::NoError));
EXPECT_TRUE(isPointerContextValid());
}
TEST_F(
SerializeExtensionStdSmartSharedPtr,
PolymorphicObservedPointerMustBeInInheritanceChainOfActuallyDeserializedObjectType2)
{
std::shared_ptr<Base> baseData1{ new MoreDerived{ 3, 7, 10 } };
std::shared_ptr<Base> baseData2{};
baseData2 = baseData1;
auto& ser = createSerializer();
ser.ext(baseData1, StdSmartPtr{});
ser.ext(baseData2, StdSmartPtr{});
std::shared_ptr<Base> baseRes1{};
std::shared_ptr<DerivedSibling> baseRes2{};
auto& des = createDeserializer();
des.ext(baseRes1, StdSmartPtr{});
des.ext(baseRes2, StdSmartPtr{});
EXPECT_THAT(baseRes2.get(), ::testing::IsNull());
auto err = des.adapter().error();
EXPECT_THAT(err, Eq(bitsery::ReaderError::InvalidPointer));
EXPECT_TRUE(isPointerContextValid());
}
struct TestSharedFromThis
: public std::enable_shared_from_this<TestSharedFromThis>
{