Skip to content

virtual_ptr: null-check copy-assignment from a smart pointer; document the dispatch's safety contract - #143

Open
jll63 wants to merge 2 commits into
developfrom
ccr-578d2c2f-tgsk9m
Open

jll63 wants to merge 2 commits into
developfrom
ccr-578d2c2f-tgsk9m

Conversation

@jll63

@jll63 jll63 commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

(Written by Claude Code, on behalf of @jll63.)

Found while auditing the library for memory-safety defects. Two commits: a fix, and the documentation the audit suggested.

Fix

The copy-assignment of a smart virtual_ptr from a smart pointer, virtual_ptr<SmartPtr>::operator=(const Other&), dereferenced the source unconditionally:

obj = other;
vp = detail::box_vptr<use_indirect_vptrs>(detail::acquire_vptr<Registry>(*other));

The constructor from the same argument and the move-assignment both guard with other ? acquire_vptr(*other) : null_vptr; this overload was the one exception. Assigning an empty std::shared_ptr lvalue therefore read the dynamic type of a null object (UBSan: "reference binding to null pointer", then a SEGV in std_rtti::dynamic_type). The rvalue path, p = dog_ptr(), was fine and was the only one the tests covered.

This aligns the overload with its siblings, and adds the lvalue case to test_smart_virtual_ptr_value_semantics.cpp, where it runs for both std::shared_ptr and boost::intrusive_ptr.

Verified with -fsanitize=address,undefined and runtime checks on: the new test fails on the unfixed header and passes with the fix; the full suite of 79 tests and 33 examples is clean under the same flags.

Documentation

Nothing else in the audit was a bug; these are the properties a security-minded user has to know and the pages did not say:

  • error_handling.adoc, new Runtime Checks section: without the policy, a call on an unregistered class reads a v-table slot that initialize never filled and calls the word it finds there; what the policy costs; when to keep it in release builds.
  • shared_libraries.adoc: between a library's unload and the next initialize, its overriders are stale function pointers in the tables, and runtime_checks does not notice by itself (finalize first makes it refuse). The library performs no synchronization: initialize, finalize and loading or unloading must not overlap with dispatch in other threads, and concurrent loads race on the registration lists.
  • registries_and_policies.adoc: the dispatch tables and static v-table pointers live in writable memory, unlike the compiler's v-tables, and may be made read-only after initialize returns.
  • Reference: initialize gains a Thread safety paragraph; the virtual_ptr pointer constructor and assignment state that the pointer must not be null (the smart-pointer overloads accept an empty pointer, this one reads the object); trace::from_env notes that the trace prints addresses, so a privileged program should not let the environment enable it.

🤖 Generated with Claude Code

https://claude.ai/code/session_016fFU6fW4SeTaPcAAEt41xu

The copy-assignment of a smart virtual_ptr from a smart pointer
dereferenced the source unconditionally, where the constructor from
the same argument and the move-assignment both check for an empty
pointer and store a null v-table pointer instead. Assigning an empty
std::shared_ptr lvalue therefore read the dynamic type of a null
object. Align the overload with its siblings, and cover the lvalue
path in the value-semantics test, which exercised only the rvalue one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fFU6fW4SeTaPcAAEt41xu
Say what runtime_checks is the only guard against: without it, a call on
an unregistered class reads a v-table slot that initialize never filled,
and calls what it finds there. Say that the library does not synchronize
initialize, finalize or the loading of a library with dispatch in other
threads; that a library's overriders become stale function pointers in
the tables between its unload and the next initialize; that the tables
live in writable memory, unlike the compiler's v-tables; that the
pointer constructor and assignment of virtual_ptr do not accept null;
and that trace::from_env hands the decision to print addresses to the
environment.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fFU6fW4SeTaPcAAEt41xu
@jll63 jll63 changed the title virtual_ptr: null-check copy-assignment from a smart pointer virtual_ptr: null-check copy-assignment from a smart pointer; document the dispatch's safety contract Nov 7, 2026
@cppalliance-bot

cppalliance-bot commented Nov 7, 2026 •

Copy link
Copy Markdown

An automated preview of the documentation is available at https://143.openmethod.prtest3.cppalliance.org/libs/openmethod/doc/html/index.html

If more commits are pushed to the pull request, the docs will rebuild at the same URL.

2026-11-07 01:57:02 UTC

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants