Skip to content

Commit 2e934c7

Browse files
committed
fix use-after-free of a blackboard entry held by getAnyLocked
1 parent 879522c commit 2e934c7

3 files changed

Lines changed: 33 additions & 3 deletions

File tree

‎include/behaviortree_cpp/utils/locked_reference.hpp‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "behaviortree_cpp/utils/safe_any.hpp"
44

5+
#include <memory>
56
#include <mutex>
67

78
namespace BT
@@ -19,7 +20,15 @@ class LockedPtr
1920
public:
2021
LockedPtr() = default;
2122

22-
LockedPtr(T* obj, std::mutex* obj_mutex) : ref_(obj), mutex_(obj_mutex)
23+
/**
24+
* @param obj the object to be protected
25+
* @param obj_mutex the mutex protecting obj
26+
* @param owner optional shared ownership of whatever holds obj and obj_mutex.
27+
* It is kept alive until this instance is destroyed, so that
28+
* neither the object nor the mutex can be freed while locked.
29+
*/
30+
LockedPtr(T* obj, std::mutex* obj_mutex, std::shared_ptr<const void> owner = {})
31+
: ref_(obj), mutex_(obj_mutex), owner_(std::move(owner))
2332
{
2433
mutex_->lock();
2534
}
@@ -39,12 +48,14 @@ class LockedPtr
3948
{
4049
std::swap(ref_, other.ref_);
4150
std::swap(mutex_, other.mutex_);
51+
std::swap(owner_, other.owner_);
4252
}
4353

4454
LockedPtr& operator=(LockedPtr&& other) noexcept
4555
{
4656
std::swap(ref_, other.ref_);
4757
std::swap(mutex_, other.mutex_);
58+
std::swap(owner_, other.owner_);
4859
return *this;
4960
}
5061

@@ -108,6 +119,7 @@ class LockedPtr
108119
private:
109120
T* ref_ = nullptr;
110121
std::mutex* mutex_ = nullptr;
122+
std::shared_ptr<const void> owner_ = nullptr;
111123
};
112124

113125
} // namespace BT

‎src/blackboard.cpp‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ AnyPtrLocked Blackboard::getAnyLocked(const std::string& key)
2525
{
2626
if(auto entry = getEntry(key))
2727
{
28-
return AnyPtrLocked(&entry->value, &entry->entry_mutex);
28+
// hand over the shared_ptr: unset()/clear() only take storage_mutex_, so the
29+
// entry can be erased while entry_mutex is still locked here.
30+
return AnyPtrLocked(&entry->value, &entry->entry_mutex, entry);
2931
}
3032
return {};
3133
}
@@ -34,7 +36,8 @@ AnyPtrLocked Blackboard::getAnyLocked(const std::string& key) const
3436
{
3537
if(auto entry = getEntry(key))
3638
{
37-
return AnyPtrLocked(&entry->value, const_cast<std::mutex*>(&entry->entry_mutex));
39+
return AnyPtrLocked(&entry->value, const_cast<std::mutex*>(&entry->entry_mutex),
40+
entry);
3841
}
3942
return {};
4043
}

‎tests/gtest_blackboard.cpp‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,21 @@ TEST(BlackboardTest, AnyPtrLocked)
303303
}
304304
#endif
305305

306+
TEST(BlackboardTest, AnyPtrLockedSurvivesUnset)
307+
{
308+
auto blackboard = Blackboard::create();
309+
blackboard->set("value", 42);
310+
311+
auto locked = blackboard->getAnyLocked("value");
312+
ASSERT_TRUE(bool(locked));
313+
314+
// unset() takes storage_mutex_ but not entry_mutex, so it can drop the last
315+
// reference to the entry we are holding the lock on.
316+
blackboard->unset("value");
317+
318+
ASSERT_EQ(locked.get()->cast<int>(), 42);
319+
}
320+
306321
TEST(BlackboardTest, SetStringView)
307322
{
308323
auto bb = Blackboard::create();

0 commit comments

Comments
 (0)