Skip to content

indirect registries: virtual_ptr stores a dangling pointer when the vptr comes from the boost_openmethod_vptr hook #87

Description

@jll63

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions