From dbb595f0543e8e2e95712c6fb4454f4c156a1ead Mon Sep 17 00:00:00 2001 From: z0mz0m Date: Mon, 27 Jul 2026 12:21:43 +0200 Subject: [PATCH] Fix use-after-free in Connection.read_fls binding: add keep_alive<0,1> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TableReader stores 'Connection& m_connection' (table_reader.hpp), but the binding returned it with return_value_policy::move and no lifetime tie. The Connection is therefore free to be collected while the reader is still alive, and the reader then dereferences freed memory. This crashes the documented one-liner from README.md and examples/python_example.py: pyfastlanes.connect().read_fls('data.fls').to_csv('decoded.csv') lldb on the unfixed build: EXC_BAD_ACCESS (code=1, address=0x6574617669727077) frame #0: fastlanes::Rowgroup::Rowgroup(RowgroupDescriptorT const&, Connection const&) (the faulting address is ASCII text — freed std::string bytes read as a pointer). It reproduces on the bundled data/example corpus. py::keep_alive<0, 1>() makes the returned reader keep its Connection alive, which is the invariant the C++ type already assumes. Verified: the README chain, an explicitly deleted connection followed by gc.collect(), and a reader returned from a function whose local connection went out of scope all now succeed and produce byte-identical output to the pattern that previously worked by accident. Co-Authored-By: Claude Fable 5 --- python/bindings/connection_binding.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/bindings/connection_binding.cpp b/python/bindings/connection_binding.cpp index 2cec549f..ad84cfbe 100644 --- a/python/bindings/connection_binding.cpp +++ b/python/bindings/connection_binding.cpp @@ -25,7 +25,11 @@ void bind_connection(py::module_& m) { &fastlanes::Connection::read_fls, py::arg("dir_path"), "Read a .fls file and return a FastLanes Reader", - py::return_value_policy::move) + py::return_value_policy::move, + // TableReader holds `Connection&`; without this the connection can be + // collected while the reader is still alive (e.g. the README's + // `connect().read_fls(p).to_csv(q)`), giving a use-after-free. + py::keep_alive<0, 1>()) .def("inline_footer", &fastlanes::Connection::inline_footer, "Enable footer inlining",