From 69d3f51d9bd2505216c4fdc04302ff6979fab093 Mon Sep 17 00:00:00 2001 From: sayed ahrar Date: Thu, 15 Oct 2026 12:28:39 +0530 Subject: [PATCH] fix: avoid use-after-free when re-entering ostream_redirect --- include/pybind11/iostream.h | 7 +++++++ tests/test_iostream.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/pybind11/iostream.h b/include/pybind11/iostream.h index df7fa3c381..2743e73388 100644 --- a/include/pybind11/iostream.h +++ b/include/pybind11/iostream.h @@ -248,9 +248,16 @@ class OstreamRedirect { void enter() { if (do_stdout_) { + // Release any existing redirect first so the stream's original buffer is + // restored before the new redirect captures it. Otherwise a second enter() + // (e.g. re-entering the same context manager) would record a pointer to the + // buffer owned by the redirect that reset() is about to destroy, leaving a + // dangling streambuf behind on exit(). + redirect_stdout.reset(); redirect_stdout.reset(new scoped_ostream_redirect()); } if (do_stderr_) { + redirect_stderr.reset(); redirect_stderr.reset(new scoped_estream_redirect()); } } diff --git a/tests/test_iostream.py b/tests/test_iostream.py index 8b11997007..be4c78ff84 100644 --- a/tests/test_iostream.py +++ b/tests/test_iostream.py @@ -284,6 +284,22 @@ def test_redirect_both(capfd): assert stream2.getvalue() == msg2 +def test_redirect_reentry(capfd): + # Re-entering the same ``ostream_redirect`` (``__enter__`` twice with no ``__exit__`` + # in between, e.g. nesting the same context manager) must not leave std::cout bound + # to a freed streambuf. Before the fix the inner redirect recorded a pointer to the + # buffer owned by the outer redirect that was about to be destroyed, so a later write + # to std::cout was a use-after-free. + redirect = m.ostream_redirect(stdout=True, stderr=False) + redirect.__enter__() + redirect.__enter__() + redirect.__exit__() + m.raw_output("alive") + stdout, stderr = capfd.readouterr() + assert stdout == "alive" + assert not stderr + + def test_move_redirect(capsys): m.move_redirect_output("before_move", "after_move") stdout, stderr = capsys.readouterr()