Skip to content

Commit 3a336a2

Browse files
authored
shared_ptr<bool> vptr_deleter_armed_flag_ptr (instead of unique_ptr) (#2882)
* shared_ptr<bool> vptr_deleter_armed_flag_ptr (instead of unique_ptr), to fix heap-use-after-free bug. * Fixing generated by some compilers in the pybind11 CI suite.
1 parent 6285177 commit 3a336a2

File tree

2 files changed

+40
-13
lines changed

2 files changed

+40
-13
lines changed

include/pybind11/detail/smart_holder_poc.h

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,8 @@ High-level aspects:
3838
* If created from an external `shared_ptr`, or a `unique_ptr` with a custom
3939
deleter, including life-time management for external objects is infeasible.
4040
41-
* The smart_holder is movable but not copyable, as a consequence of using
42-
unique_ptr for the vptr_deleter_armed_flag_ptr. Note that the bool for
43-
the flag has to live on the heap, for the smart_holder to be movable.
44-
unique_ptr is a great fit for this situation.
41+
* By choice, the smart_holder is movable but not copyable, to keep the design
42+
simple, and to guard against accidental copying overhead.
4543
*/
4644

4745
#pragma once
@@ -60,8 +58,9 @@ namespace memory {
6058

6159
template <typename T>
6260
struct guarded_builtin_delete {
63-
bool *flag_ptr;
64-
explicit guarded_builtin_delete(bool *armed_flag_ptr) : flag_ptr{armed_flag_ptr} {}
61+
std::shared_ptr<bool> flag_ptr;
62+
explicit guarded_builtin_delete(std::shared_ptr<bool> armed_flag_ptr)
63+
: flag_ptr{armed_flag_ptr} {}
6564
template <typename T_ = T,
6665
typename std::enable_if<std::is_destructible<T_>::value, int>::type = 0>
6766
void operator()(T *raw_ptr) {
@@ -80,8 +79,9 @@ struct guarded_builtin_delete {
8079

8180
template <typename T, typename D>
8281
struct guarded_custom_deleter {
83-
bool *flag_ptr;
84-
explicit guarded_custom_deleter(bool *armed_flag_ptr) : flag_ptr{armed_flag_ptr} {}
82+
std::shared_ptr<bool> flag_ptr;
83+
explicit guarded_custom_deleter(std::shared_ptr<bool> armed_flag_ptr)
84+
: flag_ptr{armed_flag_ptr} {}
8585
void operator()(T *raw_ptr) {
8686
if (*flag_ptr)
8787
D()(raw_ptr);
@@ -96,13 +96,19 @@ inline bool is_std_default_delete(const std::type_info &rtti_deleter) {
9696

9797
struct smart_holder {
9898
const std::type_info *rtti_uqp_del;
99-
std::unique_ptr<bool> vptr_deleter_armed_flag_ptr;
99+
std::shared_ptr<bool> vptr_deleter_armed_flag_ptr;
100100
std::shared_ptr<void> vptr;
101101
bool vptr_is_using_noop_deleter : 1;
102102
bool vptr_is_using_builtin_delete : 1;
103103
bool vptr_is_external_shared_ptr : 1;
104104
bool is_populated : 1;
105105

106+
// Design choice: smart_holder is movable but not copyable.
107+
smart_holder(smart_holder &&) = default;
108+
smart_holder(const smart_holder &) = delete;
109+
smart_holder &operator=(smart_holder &&) = default;
110+
smart_holder &operator=(const smart_holder &) = delete;
111+
106112
smart_holder()
107113
: rtti_uqp_del{nullptr}, vptr_is_using_noop_deleter{false},
108114
vptr_is_using_builtin_delete{false}, vptr_is_external_shared_ptr{false}, is_populated{
@@ -182,7 +188,7 @@ struct smart_holder {
182188
template <typename T>
183189
static smart_holder from_raw_ptr_unowned(T *raw_ptr) {
184190
smart_holder hld(false);
185-
hld.vptr.reset(raw_ptr, guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr.get()));
191+
hld.vptr.reset(raw_ptr, guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr));
186192
hld.vptr_is_using_noop_deleter = true;
187193
hld.is_populated = true;
188194
return hld;
@@ -213,7 +219,7 @@ struct smart_holder {
213219
static smart_holder from_raw_ptr_take_ownership(T *raw_ptr) {
214220
ensure_pointee_is_destructible<T>("from_raw_ptr_take_ownership");
215221
smart_holder hld(true);
216-
hld.vptr.reset(raw_ptr, guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr.get()));
222+
hld.vptr.reset(raw_ptr, guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr));
217223
hld.vptr_is_using_builtin_delete = true;
218224
hld.is_populated = true;
219225
return hld;
@@ -246,10 +252,10 @@ struct smart_holder {
246252
hld.vptr_is_using_builtin_delete = is_std_default_delete<T>(*hld.rtti_uqp_del);
247253
if (hld.vptr_is_using_builtin_delete) {
248254
hld.vptr.reset(unq_ptr.get(),
249-
guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr.get()));
255+
guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr));
250256
} else {
251257
hld.vptr.reset(unq_ptr.get(),
252-
guarded_custom_deleter<T, D>(hld.vptr_deleter_armed_flag_ptr.get()));
258+
guarded_custom_deleter<T, D>(hld.vptr_deleter_armed_flag_ptr));
253259
}
254260
unq_ptr.release();
255261
hld.is_populated = true;

tests/pure_cpp/smart_holder_poc_test.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,24 @@ TEST_CASE("indestructible_int-from_raw_ptr_take_ownership", "[E]") {
302302
REQUIRE_THROWS_WITH(smart_holder::from_raw_ptr_take_ownership(value),
303303
"Pointee is not destructible (from_raw_ptr_take_ownership).");
304304
}
305+
306+
TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr-outliving_smart_holder", "[S]") {
307+
// Exercises guarded_builtin_delete flag_ptr validity past destruction of smart_holder.
308+
std::shared_ptr<int> longer_living;
309+
{
310+
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
311+
longer_living = hld.as_shared_ptr<int>();
312+
}
313+
REQUIRE(*longer_living == 19);
314+
}
315+
316+
TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr-outliving_smart_holder", "[S]") {
317+
// Exercises guarded_custom_deleter flag_ptr validity past destruction of smart_holder.
318+
std::shared_ptr<int> longer_living;
319+
{
320+
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
321+
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
322+
longer_living = hld.as_shared_ptr<int>();
323+
}
324+
REQUIRE(*longer_living == 19);
325+
}

0 commit comments

Comments
 (0)