Summary
With an indirect registry, constructing a virtual_ptr (or virtual_any) from an object
whose v-table pointer comes from the boost_openmethod_vptr intrinsic hook stores a dangling
pointer.
detail::acquire_vptr prefers the hook when one is applicable, and the hook returns vptr_type
by value (it must: detail::has_vptr_fn requires the return type to be exactly vptr_type).
detail::box_vptr<true> then binds its const vptr_type& parameter to that temporary and
returns its address:
template<bool Indirect>
inline auto box_vptr(const vptr_type& vp) {
if constexpr (Indirect) {
return &vp; // address of a temporary when vp came from the hook
} else {
return vp;
}
}
The virtual_ptr stores that address; every later read of its vptr is a stack-use-after-scope.
The traits and vptr-policy branches of acquire_vptr are unaffected (they return references
into stable policy storage), as is the plain method-call path (method::vptr returns the hook's
result by value and never stores it).
Reproduction
#include <boost/openmethod.hpp>
#include <boost/openmethod/inplace_vptr.hpp>
#include <boost/openmethod/initialize.hpp>
#include <iostream>
namespace bom = boost::openmethod;
using namespace bom;
struct Indirect : inplace_vptr_base<Indirect, indirect_registry> {
virtual ~Indirect() = default;
};
int main() {
bom::initialize<indirect_registry>();
Indirect i;
virtual_ptr<Indirect, indirect_registry> p(i);
std::cout << p.vptr() << "\n"; // reads through the dangling pointer
}
Built with -fsanitize=address:
==ERROR: AddressSanitizer: stack-use-after-return
READ of size 8
#0 boost::openmethod::detail::unbox_vptr(...) core.hpp:592
#1 boost::openmethod::virtual_ptr<Indirect, indirect_registry>::vptr() const core.hpp:1073
#2 main
Address is located in stack of thread T0 at offset 32 in frame
#0 virtual_ptr<Indirect, indirect_registry>::virtual_ptr<Indirect, void>(Indirect&) core.hpp:816
Affected combinations
inplace_vptr + indirect registry + virtual_ptr/virtual_any construction. The existing
test (test_inplace_vptr.cpp, core_intrusive_vptr) only calls the hook directly; it never
constructs a virtual_ptr, which is why this went unnoticed.
- The new
openmethod_vptr TypeErasure concept + indirect registry + virtual_any
construction, for the same reason.
Non-indirect registries are fine (box_vptr<false> copies the value).
Proposed resolution
Rather than making box_vptr safe for prvalues, drop the hook branch from acquire_vptr
altogether: virtual_ptr and the intrinsic hook fill the same goal - fast access to the vptr -
so combining them buys nothing. An object that carries its own vptr does not need to be wrapped
in a virtual_ptr; and if one is constructed anyway, falling back to the vptr policy (a
one-time lookup into stable storage) is both correct and indirect-safe.
Summary
With an indirect registry, constructing a
virtual_ptr(orvirtual_any) from an objectwhose v-table pointer comes from the
boost_openmethod_vptrintrinsic hook stores a danglingpointer.
detail::acquire_vptrprefers the hook when one is applicable, and the hook returnsvptr_typeby value (it must:
detail::has_vptr_fnrequires the return type to be exactlyvptr_type).detail::box_vptr<true>then binds itsconst vptr_type¶meter to that temporary andreturns its address:
The
virtual_ptrstores that address; every later read of its vptr is a stack-use-after-scope.The traits and
vptr-policy branches ofacquire_vptrare unaffected (they return referencesinto stable policy storage), as is the plain method-call path (
method::vptrreturns the hook'sresult by value and never stores it).
Reproduction
Built with
-fsanitize=address:Affected combinations
inplace_vptr+ indirect registry +virtual_ptr/virtual_anyconstruction. The existingtest (
test_inplace_vptr.cpp,core_intrusive_vptr) only calls the hook directly; it neverconstructs a
virtual_ptr, which is why this went unnoticed.openmethod_vptrTypeErasure concept + indirect registry +virtual_anyconstruction, for the same reason.
Non-indirect registries are fine (
box_vptr<false>copies the value).Proposed resolution
Rather than making
box_vptrsafe for prvalues, drop the hook branch fromacquire_vptraltogether:
virtual_ptrand the intrinsic hook fill the same goal - fast access to the vptr -so combining them buys nothing. An object that carries its own vptr does not need to be wrapped
in a
virtual_ptr; and if one is constructed anyway, falling back to thevptrpolicy (aone-time lookup into stable storage) is both correct and indirect-safe.