VYPR
High severity7.3NVD Advisory· Published May 26, 2026

CVE-2026-9521

CVE-2026-9521

Description

A security vulnerability has been detected in fraillt bitsery up to 5.2.4. Affected is the function loadFromSharedState in the library include/bitsery/ext/std_smart_ptr.h. Such manipulation leads to improper validation of specified type of input. It is possible to launch the attack remotely. The exploit has been disclosed publicly and may be used. Upgrading to version 5.2.5 is able to address this issue. The name of the patch is 66d16516e24893bebc1c8af52bf2fe9ad0735061. Upgrading the affected component is advised.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Type confusion in bitsery's shared pointer deserialization allows remote attackers to achieve arbitrary code execution via crafted payload.

Vulnerability

A type confusion vulnerability exists in bitsery versions up to and including 5.2.4. The flaw resides in the loadFromSharedState function within include/bitsery/ext/std_smart_ptr.h. During deserialization of shared pointers, the library fails to properly validate the target type, allowing a crafted payload to assign a shared pointer to an incompatible type [1][2].

Exploitation

An attacker can exploit this vulnerability remotely by providing a malicious serialized archive to an application using bitsery to deserialize untrusted data. The exploit does not require authentication and is publicly documented with proof-of-concept examples demonstrating address leakage, memory read, and vtable hijacking [3].

Impact

Successful exploitation can lead to sensitive information disclosure (ASLR bypass), arbitrary memory read, denial-of-service, and potentially arbitrary code execution through control flow hijacking [3]. The impact is severe, as it can compromise the confidentiality, integrity, and availability of the affected system.

Mitigation

The vulnerability is fixed in bitsery version 5.2.5, released on 2025-10-09 [1]. The fix is identified in commit 66d16516e24893bebc1c8af52bf2fe9ad0735061 [2]. Users should upgrade to version 5.2.5 or later. No workarounds have been disclosed.

AI Insight generated on May 26, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Fraillt/Bitseryreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <=5.2.4

Patches

1
66d16516e248

Fix shared polymorphic poiner like type serialization/deserialization

https://github.com/fraillt/bitseryMindaugas VinkelisOct 2, 2025via nvd-ref
5 files changed · +391 101
  • include/bitsery/ext/pointer.h+23 45 modified
    @@ -104,7 +104,7 @@ struct PtrObserverManager
     
       static void destroyPolymorphic(T& obj,
                                      MemResourceBase*,
    -                                 PolymorphicHandlerBase&)
    +                                 const std::shared_ptr<PolymorphicHandlerBase>&)
       {
         obj = nullptr;
       }
    @@ -130,47 +130,22 @@ struct NonPtrManager
     
       static void create(T&, MemResourceBase*, size_t) {}
     
    -  static void createPolymorphic(T&, MemResourceBase*, PolymorphicHandlerBase&)
    +  static void createPolymorphic(T&,
    +                                MemResourceBase*,
    +                                const std::shared_ptr<PolymorphicHandlerBase>&)
       {
       }
     
       static void destroy(T&, MemResourceBase*, size_t) {}
     
    -  static void destroyPolymorphic(T&, MemResourceBase*, PolymorphicHandlerBase&)
    +  static void destroyPolymorphic(T&,
    +                                 MemResourceBase*,
    +                                 const std::shared_ptr<PolymorphicHandlerBase>&)
       {
       }
       // LCOV_EXCL_STOP
     };
     
    -// this class is used by NonPtrManager
    -struct NoRTTI
    -{
    -  template<typename TBase>
    -  static size_t get(TBase&)
    -  {
    -    return 0;
    -  }
    -
    -  template<typename TBase>
    -  static constexpr size_t get()
    -  {
    -    return 0;
    -  }
    -
    -  template<typename TBase, typename TDerived>
    -  static constexpr TDerived* cast(TBase* obj)
    -  {
    -    static_assert(!std::is_pointer<TDerived>::value, "");
    -    return dynamic_cast<TDerived*>(obj);
    -  }
    -
    -  template<typename TBase>
    -  static constexpr bool isPolymorphic()
    -  {
    -    return false;
    -  }
    -};
    -
     }
     
     template<typename RTTI>
    @@ -179,31 +154,34 @@ using PointerOwnerBase =
                                                 PolymorphicContext,
                                                 RTTI>;
     
    -using PointerOwner = PointerOwnerBase<StandardRTTI>;
    -
    -using PointerObserver =
    +template<typename RTTI>
    +using PointerObserverBase =
       pointer_utils::PointerObjectExtensionBase<pointer_details::PtrObserverManager,
                                                 PolymorphicContext,
    -                                            pointer_details::NoRTTI>;
    +                                            RTTI>;
     
     // inherit from PointerObjectExtensionBase in order to specify
     // PointerType::NotNull
    -class ReferencedByPointer
    +template<typename RTTI>
    +class ReferencedByPointerBase
       : public pointer_utils::PointerObjectExtensionBase<
           pointer_details::NonPtrManager,
           PolymorphicContext,
    -      pointer_details::NoRTTI>
    +      RTTI>
     {
     public:
    -  ReferencedByPointer()
    +  ReferencedByPointerBase()
         : pointer_utils::PointerObjectExtensionBase<pointer_details::NonPtrManager,
                                                     PolymorphicContext,
    -                                                pointer_details::NoRTTI>(
    -        PointerType::NotNull)
    +                                                RTTI>(PointerType::NotNull)
       {
       }
     };
     
    +using PointerOwner = PointerOwnerBase<StandardRTTI>;
    +using PointerObserver = PointerObserverBase<StandardRTTI>;
    +using ReferencedByPointer = ReferencedByPointerBase<StandardRTTI>;
    +
     }
     
     namespace traits {
    @@ -219,8 +197,8 @@ struct ExtensionTraits<ext::PointerOwnerBase<RTTI>, T*>
         !RTTI::template isPolymorphic<TValue>();
     };
     
    -template<typename T>
    -struct ExtensionTraits<ext::PointerObserver, T*>
    +template<typename T, typename RTTI>
    +struct ExtensionTraits<ext::PointerObserverBase<RTTI>, T*>
     {
       // although pointer observer doesn't serialize anything, but we still add
       // value overload support to be consistent with pointer owners observer only
    @@ -231,8 +209,8 @@ struct ExtensionTraits<ext::PointerObserver, T*>
       static constexpr bool SupportLambdaOverload = false;
     };
     
    -template<typename T>
    -struct ExtensionTraits<ext::ReferencedByPointer, T>
    +template<typename T, typename RTTI>
    +struct ExtensionTraits<ext::ReferencedByPointerBase<RTTI>, T>
     {
       // allow everything, because it is serialized as regular type, except it also
       // creates pointerId that is required by NonOwningPointer to work
    
  • include/bitsery/ext/std_smart_ptr.h+18 1 modified
    @@ -198,10 +198,27 @@ struct SmartPtrOwnerManager
         state.obj = std::shared_ptr<TElement>(obj);
       }
     
    +  static void saveToSharedStatePolymorphic(TSharedState& state, T& obj)
    +  {
    +    state.obj = std::shared_ptr<TElement>(obj);
    +  }
    +
       static void loadFromSharedState(TSharedState& state, T& obj)
       {
         // reinterpret_pointer_cast is only since c++17
    -    auto p = reinterpret_cast<TElement*>(state.obj.get());
    +    auto v = state.obj.get();
    +    auto p = reinterpret_cast<TElement*>(v);
    +    obj = std::shared_ptr<TElement>(state.obj, p);
    +  }
    +
    +  static void loadFromSharedStatePolymorphic(TSharedState& state, T& obj)
    +  {
    +    // TODO Fix pointer addresses in case objects are deserialized using
    +    // different bases
    +
    +    // reinterpret_pointer_cast is only since c++17
    +    auto v = state.obj.get();
    +    auto p = reinterpret_cast<TElement*>(v);
         obj = std::shared_ptr<TElement>(state.obj, p);
       }
     };
    
  • include/bitsery/ext/utils/pointer_utils.h+75 18 modified
    @@ -120,10 +120,12 @@ struct PLCInfoSerializer : PLCInfo
     struct PLCInfoDeserializer : PLCInfo
     {
       PLCInfoDeserializer(void* ptr,
    +                      size_t sharedTypeId_,
                           PointerOwnershipType ownershipType_,
                           MemResourceBase* memResource_)
         : PLCInfo(ownershipType_)
         , ownerPtr{ ptr }
    +    , sharedTypeId{ sharedTypeId_ }
         , memResource{ memResource_ }
         , observersList{ StdPolyAlloc<std::reference_wrapper<void*>>{
             memResource_ } } {};
    @@ -157,6 +159,9 @@ struct PLCInfoDeserializer : PLCInfo
       }
     
       void* ownerPtr;
    +  // used for polymorphic types in order to identify
    +  // if shared objects can be assigned
    +  size_t sharedTypeId;
       MemResourceBase* memResource;
       std::vector<std::reference_wrapper<void*>,
                   StdPolyAlloc<std::reference_wrapper<void*>>>
    @@ -254,8 +259,8 @@ class PointerLinkingContextDeserialization
     
       PLCInfoDeserializer& getInfoById(size_t id, PointerOwnershipType ptrType)
       {
    -    auto res =
    -      _idMap.emplace(id, PLCInfoDeserializer{ nullptr, ptrType, _memResource });
    +    auto res = _idMap.emplace(
    +      id, PLCInfoDeserializer{ nullptr, 0, ptrType, _memResource });
         auto& ptrInfo = res.first->second;
         if (!res.second)
           ptrInfo.update(ptrType);
    @@ -352,8 +357,8 @@ class PointerObjectExtensionBase
         if (ptr) {
           auto& ctx = ser.template context<
             pointer_utils::PointerLinkingContextSerialization>();
    -      auto& ptrInfo =
    -        ctx.getInfoByPtr(getBasePtr(ptr), TPtrManager<T>::getOwnership());
    +      auto& ptrInfo = ctx.getInfoByPtr(getRootPtr(ser, ptr, IsPolymorphic<T>{}),
    +                                       TPtrManager<T>::getOwnership());
           details::writeSize(ser.adapter(), ptrInfo.id);
           if (TPtrManager<T>::getOwnership() != PointerOwnershipType::Observer) {
             if (!ptrInfo.isSharedProcessed)
    @@ -411,7 +416,7 @@ class PointerObjectExtensionBase
         const auto& ctx = des.template context<TPolymorphicContext<RTTI>>();
         auto ptr = TPtrManager<TObj>::getPtr(obj);
         TPtrManager<TObj>::destroyPolymorphic(
    -      obj, memResource, ctx.getPolymorphicHandler(*ptr));
    +      obj, memResource, ctx.getPolymorphicHandler(ptr));
       }
     
       template<typename Des, typename TObj>
    @@ -426,16 +431,23 @@ class PointerObjectExtensionBase
           RTTI::template get<typename TPtrManager<TObj>::TElement>());
       }
     
    -  template<typename T>
    -  const void* getBasePtr(const T* ptr) const
    +  template<typename Ser, typename T>
    +  const void* getRootPtr(Ser&, const T* ptr, std::false_type) const
       {
    -    // todo implement handling of types with virtual inheritance
    -    // this is required to correctly track same object, when one object is
    -    // derived and other is base class e.g. shared_ptr<Base> and
    -    // weak_ptr<Derived> or pointer observer Base*
         return ptr;
       }
     
    +  // same pointer can be accessed through different types with the same base
    +  // e.g. if we have Base class and Derived(Base) we'll get different pointer
    +  // address depending if we access it through Base or Derived
    +  // this function always returns "root" (Base) pointer.
    +  template<typename Ser, typename T>
    +  const void* getRootPtr(Ser& ser, const T* ptr, std::true_type) const
    +  {
    +    const auto& ctx = ser.template context<TPolymorphicContext<RTTI>>();
    +    return ctx.getPolymorphicHandler(ptr)->getRootPtr(ptr);
    +  }
    +
       template<typename Ser, typename TPtr, typename Fnc>
       void serializeImpl(Ser& ser, TPtr& ptr, Fnc&&, std::true_type) const
       {
    @@ -506,8 +518,11 @@ class PointerObjectExtensionBase
                            std::true_type,
                            OwnershipType<PointerOwnershipType::SharedOwner>) const
       {
    +    const auto& ctx = des.template context<TPolymorphicContext<RTTI>>();
    +    const size_t baseTypeId =
    +      RTTI::template get<typename TPtrManager<T>::TElement>();
    +    size_t deserializedTypeId = 0;
         if (!ptrInfo.sharedState) {
    -      const auto& ctx = des.template context<TPolymorphicContext<RTTI>>();
           ctx.deserialize(
             des,
             TPtrManager<T>::getPtr(obj),
    @@ -521,12 +536,46 @@ class PointerObjectExtensionBase
              memResource](const std::shared_ptr<PolymorphicHandlerBase>& handler) {
               TPtrManager<T>::destroyPolymorphic(obj, memResource, handler);
             });
    -      if (!ptrInfo.sharedState)
    -        TPtrManager<T>::saveToSharedState(
    +      if (!ptrInfo.sharedState) {
    +        TPtrManager<T>::saveToSharedStatePolymorphic(
               createAndGetSharedStateObj<T>(ptrInfo), obj);
    +      }
    +      ptrInfo.sharedTypeId =
    +        ctx.getPolymorphicHandler(TPtrManager<T>::getPtr(obj))
    +          ->getDerivedTypeId();
    +      // since we just deserialized an object, we can skip checking hierarchy
    +      // chain by assigning baseType id instead of derived type id
    +      deserializedTypeId = baseTypeId;
    +    } else {
    +      deserializedTypeId = ptrInfo.sharedTypeId;
         }
    -    TPtrManager<T>::loadFromSharedState(getSharedStateObj<T>(ptrInfo), obj);
    -    ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
    +
    +    if (canAssignToShared(baseTypeId, deserializedTypeId, ctx)) {
    +      TPtrManager<T>::loadFromSharedStatePolymorphic(
    +        getSharedStateObj<T>(ptrInfo), obj);
    +      ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
    +    } else {
    +      des.adapter().error(ReaderError::InvalidPointer);
    +    }
    +  }
    +
    +  // check if actual deserialized type can be assigned to the base type
    +  // (statically typed)
    +  bool canAssignToShared(size_t baseTypeId,
    +                         size_t deserializedTypeId,
    +                         const TPolymorphicContext<RTTI>& ctx) const
    +  {
    +    if (baseTypeId == deserializedTypeId)
    +      return true;
    +    auto bases = ctx.getDirectBases(deserializedTypeId);
    +    if (bases) {
    +      for (auto typeId : *bases) {
    +        if (canAssignToShared(baseTypeId, typeId, ctx)) {
    +          return true;
    +        }
    +      }
    +    }
    +    return false;
       }
     
       template<typename Des, typename T, typename Fnc>
    @@ -538,6 +587,8 @@ class PointerObjectExtensionBase
                            std::false_type,
                            OwnershipType<PointerOwnershipType::SharedOwner>) const
       {
    +    const size_t baseTypeId =
    +      RTTI::template get<typename TPtrManager<T>::TElement>();
         if (!ptrInfo.sharedState) {
           auto ptr = TPtrManager<T>::getPtr(obj);
           if (ptr) {
    @@ -552,9 +603,15 @@ class PointerObjectExtensionBase
             ptr = TPtrManager<T>::getPtr(obj);
           }
           fnc(des, *ptr);
    +      ptrInfo.sharedTypeId =
    +        RTTI::template get<typename TPtrManager<T>::TElement>();
    +    }
    +    if (baseTypeId == ptrInfo.sharedTypeId) {
    +      TPtrManager<T>::loadFromSharedState(getSharedStateObj<T>(ptrInfo), obj);
    +      ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
    +    } else {
    +      des.adapter().error(ReaderError::InvalidPointer);
         }
    -    TPtrManager<T>::loadFromSharedState(getSharedStateObj<T>(ptrInfo), obj);
    -    ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
       }
     
       template<typename Des, typename T, typename Fnc, typename isPolymorph>
    
  • include/bitsery/ext/utils/polymorphism_utils.h+155 37 modified
    @@ -74,10 +74,18 @@ class PolymorphicHandlerBase
     
       virtual void process(void* ser, void* obj) const = 0;
     
    +  virtual void* getRootPtr(const void* obj) const = 0;
    +
    +  virtual size_t getDerivedTypeId() const = 0;
    +
       virtual ~PolymorphicHandlerBase() = default;
     };
     
    -template<typename RTTI, typename TSerializer, typename TBase, typename TDerived>
    +template<typename RTTI,
    +         typename TSerializer,
    +         typename TRoot,
    +         typename TBase,
    +         typename TDerived>
     class PolymorphicHandler : public PolymorphicHandlerBase
     {
     public:
    @@ -97,6 +105,17 @@ class PolymorphicHandler : public PolymorphicHandlerBase
         static_cast<TSerializer*>(ser)->object(*fromBase(obj));
       }
     
    +  void* getRootPtr(const void* obj) const final
    +  {
    +    return RTTI::template cast<TBase, TRoot>(
    +      static_cast<TBase*>(const_cast<void*>(obj)));
    +  }
    +
    +  size_t getDerivedTypeId() const final
    +  {
    +    return RTTI::template get<TDerived>();
    +  }
    +
     private:
       TDerived* fromBase(void* obj) const
       {
    @@ -109,6 +128,35 @@ class PolymorphicHandler : public PolymorphicHandlerBase
       }
     };
     
    +// Even though we don't serialize/deserialize abstract classes
    +// object might still be accessed through abstract class, hence we need this
    +// for type information
    +template<typename RTTI, typename TRoot, typename TBase, typename TDerived>
    +class AbstractPolymorphicHandler : public PolymorphicHandlerBase
    +{
    +public:
    +  void* create(const pointer_utils::PolyAllocWithTypeId& alloc) const
    +  {
    +    assert(false);
    +    return nullptr;
    +  }
    +
    +  void destroy(const pointer_utils::PolyAllocWithTypeId& alloc, void* ptr) const
    +  {
    +    assert(false);
    +  };
    +
    +  void process(void* ser, void* obj) const { assert(false); }
    +
    +  void* getRootPtr(const void* obj) const
    +  {
    +    assert(false);
    +    return nullptr;
    +  }
    +
    +  size_t getDerivedTypeId() const { return RTTI::template get<TDerived>(); };
    +};
    +
     template<typename RTTI>
     class PolymorphicContext
     {
    @@ -137,47 +185,54 @@ class PolymorphicContext
       template<typename TSerializer,
                template<typename>
                class THierarchy,
    +           typename TRoot,
                typename TBase,
                typename TDerived>
    -  void add()
    +  void add(size_t depth)
       {
    -    addToMap<TSerializer, TBase, TDerived>(std::is_abstract<TDerived>{});
    -    addChilds<TSerializer, THierarchy, TBase, TDerived>(
    -      typename THierarchy<TDerived>::Childs{});
    +    addToMap<TSerializer, TRoot, TBase, TDerived>(depth == 1,
    +                                                  std::is_abstract<TDerived>{});
    +    addChilds<TSerializer, THierarchy, TRoot, TBase, TDerived>(
    +      depth + 1, typename THierarchy<TDerived>::Childs{});
       }
     
       template<typename TSerializer,
                template<typename>
                class THierarchy,
    +           typename TRoot,
                typename TBase,
                typename TDerived,
                typename T1,
                typename... Tn>
    -  void addChilds(PolymorphicClassesList<T1, Tn...>)
    +  void addChilds(size_t depth, PolymorphicClassesList<T1, Tn...>)
       {
         static_assert(std::is_base_of<TDerived, T1>::value,
                       "PolymorphicBaseClass<TBase> must derive a list of derived "
                       "classes from TBase.");
    -    add<TSerializer, THierarchy, TBase, T1>();
    -    addChilds<TSerializer, THierarchy, TBase, TDerived>(
    -      PolymorphicClassesList<Tn...>{});
    -    // iterate through derived class hierarchy as well
    -    add<TSerializer, THierarchy, T1, T1>();
    +    add<TSerializer, THierarchy, TRoot, TBase, T1>(depth);
    +    addChilds<TSerializer, THierarchy, TRoot, TBase, TDerived>(
    +      depth, PolymorphicClassesList<Tn...>{});
    +    add<TSerializer, THierarchy, TRoot, T1, T1>(0);
       }
     
       template<typename TSerializer,
                template<typename>
                class THierarchy,
    +           typename TRoot,
                typename TBase,
                typename TDerived>
    -  void addChilds(PolymorphicClassesList<>)
    +  void addChilds(size_t, PolymorphicClassesList<>)
       {
       }
     
    -  template<typename TSerializer, typename TBase, typename TDerived>
    -  void addToMap(std::false_type)
    +  template<typename TSerializer,
    +           typename TRoot,
    +           typename TBase,
    +           typename TDerived>
    +  void addToMap(bool directBase, std::false_type)
       {
    -    using THandler = PolymorphicHandler<RTTI, TSerializer, TBase, TDerived>;
    +    using THandler =
    +      PolymorphicHandler<RTTI, TSerializer, TRoot, TBase, TDerived>;
         BaseToDerivedKey key{ RTTI::template get<TBase>(),
                               RTTI::template get<TDerived>() };
         pointer_utils::StdPolyAlloc<THandler> alloc{ _memResource };
    @@ -201,12 +256,51 @@ class PolymorphicContext
           }
           it->second.push_back(key.derivedHash);
         }
    +    if (directBase) {
    +      auto it = _derivedToBaseArray.find(key.derivedHash);
    +      if (it == _derivedToBaseArray.end()) {
    +        it = _derivedToBaseArray
    +               .emplace(std::piecewise_construct,
    +                        std::forward_as_tuple(key.derivedHash),
    +                        std::forward_as_tuple(
    +                          pointer_utils::StdPolyAlloc<size_t>{ _memResource }))
    +               .first;
    +      }
    +      it->second.push_back(key.baseHash);
    +    }
       }
     
    -  template<typename TSerializer, typename TBase, typename TDerived>
    -  void addToMap(std::true_type)
    +  template<typename TSerializer,
    +           typename TRoot,
    +           typename TBase,
    +           typename TDerived>
    +  void addToMap(bool directBase, std::true_type)
       {
    -    // cannot add abstract class
    +    using THandler = AbstractPolymorphicHandler<RTTI, TRoot, TBase, TDerived>;
    +    BaseToDerivedKey key{ RTTI::template get<TBase>(),
    +                          RTTI::template get<TDerived>() };
    +    pointer_utils::StdPolyAlloc<THandler> alloc{ _memResource };
    +    auto ptr = alloc.allocate(1);
    +    std::shared_ptr<THandler> handler(
    +      new (ptr) THandler{},
    +      [alloc](THandler* data) mutable {
    +        data->~THandler();
    +        alloc.deallocate(data, 1);
    +      },
    +      alloc);
    +    _baseToDerivedMap.emplace(key, std::move(handler)).second;
    +    if (directBase) {
    +      auto it = _derivedToBaseArray.find(key.derivedHash);
    +      if (it == _derivedToBaseArray.end()) {
    +        it = _derivedToBaseArray
    +               .emplace(std::piecewise_construct,
    +                        std::forward_as_tuple(key.derivedHash),
    +                        std::forward_as_tuple(
    +                          pointer_utils::StdPolyAlloc<size_t>{ _memResource }))
    +               .first;
    +      }
    +      it->second.push_back(key.baseHash);
    +    }
       }
     
       MemResourceBase* _memResource;
    @@ -234,6 +328,17 @@ class PolymorphicContext
                     std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>>>>
         _baseToDerivedArray;
     
    +  // Used to iterate through hierarchy chain from most derived to the base(s)
    +  std::unordered_map<
    +    size_t,
    +    std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>,
    +    std::hash<size_t>,
    +    std::equal_to<size_t>,
    +    pointer_utils::StdPolyAlloc<
    +      std::pair<const size_t,
    +                std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>>>>
    +    _derivedToBaseArray;
    +
     public:
       explicit PolymorphicContext(MemResourceBase* memResource = nullptr)
         : _memResource{ memResource }
    @@ -244,6 +349,11 @@ class PolymorphicContext
             std::pair<const size_t,
                       std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>>>{
             memResource } }
    +    , _derivedToBaseArray{ pointer_utils::StdPolyAlloc<
    +        std::pair<const size_t,
    +                  std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>>>{
    +        memResource } }
    +
       {
       }
     
    @@ -269,7 +379,7 @@ class PolymorphicContext
                typename... Tn>
       void registerBasesList(PolymorphicClassesList<T1, Tn...>)
       {
    -    add<TSerializer, THierarchy, T1, T1>();
    +    add<TSerializer, THierarchy, T1, T1, T1>(0);
         registerBasesList<TSerializer, THierarchy>(PolymorphicClassesList<Tn...>{});
       }
     
    @@ -278,18 +388,6 @@ class PolymorphicContext
       {
       }
     
    -  // optional method, in case you want to construct base class hierarchy your
    -  // self
    -  template<typename TSerializer, typename TBase, typename TDerived>
    -  void registerSingleBaseBranch()
    -  {
    -    static_assert(std::is_base_of<TBase, TDerived>::value,
    -                  "TDerived must be derived from TBase");
    -    static_assert(!std::is_abstract<TDerived>::value,
    -                  "TDerived cannot be abstract");
    -    addToMap<TSerializer, TBase, TDerived>(std::false_type{});
    -  }
    -
       template<typename Serializer, typename TBase>
       void serialize(Serializer& ser, TBase& obj) const
       {
    @@ -338,7 +436,7 @@ class PolymorphicContext
           // if object is null or different type, create new and assign it
           if (obj == nullptr || RTTI::template get<TBase>(*obj) != derivedHash) {
             if (obj) {
    -          destroyFnc(getPolymorphicHandler(*obj));
    +          destroyFnc(getPolymorphicHandler(obj));
             }
             obj = createFnc(handler);
           }
    @@ -349,12 +447,32 @@ class PolymorphicContext
     
       template<typename TBase>
       const std::shared_ptr<PolymorphicHandlerBase>& getPolymorphicHandler(
    -    TBase& obj) const
    +    TBase* obj) const
       {
    -    auto deleteHandlerIt = _baseToDerivedMap.find(BaseToDerivedKey{
    -      RTTI::template get<TBase>(), RTTI::template get<TBase>(obj) });
    -    assert(deleteHandlerIt != _baseToDerivedMap.end());
    -    return deleteHandlerIt->second;
    +    auto it = _baseToDerivedMap.find(BaseToDerivedKey{
    +      RTTI::template get<TBase>(), RTTI::template get<TBase>(*obj) });
    +    assert(it != _baseToDerivedMap.end());
    +    return it->second;
    +  }
    +
    +  template<typename TBase>
    +  const std::shared_ptr<PolymorphicHandlerBase>& getPolymorphicHandler() const
    +  {
    +    auto it = _baseToDerivedMap.find(BaseToDerivedKey{
    +      RTTI::template get<TBase>(), RTTI::template get<TBase>() });
    +    assert(it != _baseToDerivedMap.end());
    +    return it->second;
    +  }
    +
    +  const std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>*
    +  getDirectBases(size_t derivedTypeId) const
    +  {
    +    auto it = _derivedToBaseArray.find(derivedTypeId);
    +    if (it != _derivedToBaseArray.end()) {
    +      return &it->second;
    +    } else {
    +      return nullptr;
    +    }
       }
     };
     
    
  • tests/serialization_ext_std_smart_ptr.cpp+120 0 modified
    @@ -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>
     {
    

Vulnerability mechanics

Root cause

"Missing type-compatibility validation in `loadFromSharedState` allows a shared pointer to be assigned to an object of a different, incompatible type during deserialization."

Attack vector

An attacker sends a crafted serialized payload that causes a shared pointer to be deserialized as a different type than the one it was originally serialized as. During deserialization, the library previously trusted the input to determine whether a shared pointer referred to an already-deserialized object; if so, it would assign the pointer without verifying that the observed type is actually compatible with the statically declared type [ref_id=1]. This allows the attacker to make a `shared_ptr<Base>` point to memory laid out as a completely unrelated type (e.g. `std::vector`), potentially leaking internal object addresses and bypassing ASLR [ref_id=1]. The attack is remote, requires no authentication, and has low complexity (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L).

Affected code

The vulnerability resides in `include/bitsery/ext/std_smart_ptr.h` in the `loadFromSharedState` function, and in the deserialization logic within `include/bitsery/ext/utils/pointer_utils.h` (the `PointerObjectExtensionBase` class). The patch also modifies `include/bitsery/ext/utils/polymorphism_utils.h` to add a `_derivedToBaseArray` map and a `canAssignToShared` check.

What the fix does

The patch adds a `_derivedToBaseArray` map to `PolymorphicContext` that records which base types each derived type directly inherits from. During deserialization of shared polymorphic pointers, the new `canAssignToShared` method walks this hierarchy chain to verify that the actual deserialized type is in the inheritance chain of the statically declared pointer type [patch_id=2539783]. If the types are incompatible, the deserializer sets a `ReaderError::InvalidPointer` error instead of blindly assigning the pointer. For non-polymorphic shared pointers, a similar direct type-ID equality check is added. The patch also introduces `AbstractPolymorphicHandler` to properly register abstract base classes in the hierarchy, and fixes pointer-address tracking for virtual inheritance via `getRootPtr`.

Preconditions

  • configThe application must use bitsery's StdSmartPtr extension to deserialize shared pointers from untrusted data.
  • networkThe attacker must be able to supply a crafted serialized payload over the network.
  • authNo authentication or prior access is required.

Reproduction

The test file `tests/serialization_ext_std_smart_ptr.cpp` (added in the patch) contains three reproduction test cases. The first test, `NonPolymorphicObservedPointerMustMatchActuallyDeserializedObjectType`, serializes a `shared_ptr<vector<uint8_t>>` and a `shared_ptr<MightMatchVecLayout>`, then hacks the buffer so that during deserialization the second pointer references the first object's ID. Without the fix, the deserializer would blindly assign the `MightMatchVecLayout` pointer to the `vector`'s memory, leaking its internal representation. The second and third tests (`PolymorphicObservedPointerMustBeInInheritanceChain...`) verify that a polymorphic shared pointer deserialized through a base type can only be assigned to a derived type that is actually in its inheritance chain, not to an unrelated sibling class.

Generated on May 26, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.