Skip to content

Experimental work on top of PR #6096#6111

Open
rwgk wants to merge 9 commits into
pybind:masterfrom
rwgk:fix-string-view-life-support-no-frame_rwgk
Open

Experimental work on top of PR #6096#6111
rwgk wants to merge 9 commits into
pybind:masterfrom
rwgk:fix-string-view-life-support-no-frame_rwgk

Conversation

@rwgk

@rwgk rwgk commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Meant for feeding back into #6096

Uses latest master, reverts 99bf08b, adds documentation, adds three commits with additional unit tests.

All changes made by codex gpt-5.6-sol

Trying CI here.


📚 Documentation preview 📚: https://pybind11--6111.org.readthedocs.build/

henryiii and others added 9 commits June 29, 2026 00:42
…ind#6092)"

This re-applies pybind#6092 (reverting pybind#6097) so the follow-up fixes in this PR can build on it.

Assisted-by: ClaudeCode:claude-opus-4.8
PR pybind#6092 added loader_life_support::add_patient(src) to keep the source
object alive when loading a string view, fixing a real use-after-free when
a container of views is built from a non-sequence iterable (e.g. a
generator): list_caster materializes a temporary tuple that owns the
strings and destroys it when load() returns, before the bound function
body runs.

add_patient throws when there is no life support frame, so casting to a
view outside a bound function (e.g. a manual py::cast<std::string_view>)
now raises instead of relying on the caller-owned source, a regression
from pybind#6092.

For these view-into-src cases registration is best effort: inside a bound
function it keeps src alive (fixing the UAF), and outside one the caller
owns src's lifetime as before. Add try_add_patient(), which returns false
instead of throwing when there is no frame, and use it at the three view
load sites. add_patient() keeps its strict contract for value-creating
conversions.

Assisted-by: ClaudeCode:claude-opus-4.8
Refine the previous commit. Best-effort registration (try_add_patient)
silently produces a dangling view when a container of views is built from
a generator outside a bound function: there the materialized temporary is
released before the view is used, and with no frame nothing keeps it
alive. Such a cast cannot be made safe, so it should fail loudly, while a
view into a durable, caller-owned object needs no life support at all.

The view caster cannot tell a durable source from a pybind11-managed
transient one; that provenance lives in the container caster. Introduce an
ambient transient_source_guard that the list, set, map, and array casters
set around their generator/materialized paths, and have the string caster
keep the source alive only when loading from a transient source (via the
throwing add_patient, so try_add_patient is no longer needed). This means:

- views into durable sources (direct arguments, sequences, manual casts)
  add no life support and no longer throw outside a bound function, and
- a generator used outside a frame throws, rather than silently dangling.

The guard restores (rather than clears) the previous value, so a durable
container nested in a transient one is correctly treated as transient.

Verified with AddressSanitizer: the in-frame generator case is clean, the
out-of-frame durable cases succeed, and the out-of-frame generator case
throws.

Assisted-by: ClaudeCode:claude-opus-4.8
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.

2 participants