Fix dangling pointers in DBus during Shutdown.
@alimariam reported the dangling pointer detector found a new dangling
pointer when running tests on linux Workstation.
The error is:
```
The memory was freed at:
#3 allocator_shim::internal::PartitionFree()
#4 bluez::BluezDBusThreadManager::~BluezDBusThreadManager()
#5 bluez::BluezDBusThreadManager::Shutdown()
#6 ChromeBrowserMainPartsLinux::PostDestroyThreads()
#7 content::BrowserMainLoop::ShutdownThreadsAndCleanUp()
#8 content::BrowserMainRunnerImpl::Shutdown()
#9 content::BrowserMain()
#10 content::RunBrowserProcessMain()
#11 content::ContentMainRunnerImpl::RunBrowser()
#12 content::ContentMainRunnerImpl::Run()
#13 content::RunContentProcess()
#14 content::ContentMain()
#15 ChromeMain
The dangling raw_ptr was released at:
#3 base::internal::RawPtrBackupRefImpl<>::ReleaseInternal()
#4 dbus::ObjectManager::~ObjectManager()
#5 std::__Cr::__tuple_impl<>::~__tuple_impl()
#6 base::internal::BindState<>::Destroy()
#7 base::[...]::LazilyDeallocatedDeque<>::Ring::~Ring()
#8 base::[...]::TaskQueueImpl::UnregisterTaskQueue()
#9 base::[...]::SequenceManagerImpl::UnregisterTaskQueueImpl()
#10 base::sequence_manager::TaskQueue::ShutdownTaskQueue()
#11 content::BrowserTaskQueues::~BrowserTaskQueues()
#12 content::BrowserUIThreadScheduler::~BrowserUIThreadScheduler()
#13 content::BrowserTaskExecutor::[...]::~UIThreadExecutor()
#14 content::BrowserTaskExecutor::[...]::~UIThreadExecutor()
#15 content::BrowserTaskExecutor::Shutdown()
#16 content::ContentMainRunnerImpl::Shutdown()
#17 content::RunContentProcess()
#18 content::ContentMain()
#19 ChromeMain
```
Diagnostic:
- `bluez::BluezDBusThreadManager` owns a `dbus::Bus` as `system_bus`.
- `dbus::Bus` owns:
- The set of `dbus::ObjectManager` as `object_manager_table_`.
- The DBus task runner as `dbus_task_runner_`.
- The `dbus::ObjectManager` references `dbus::Bus` via `bus_`.
So far so good, the ownership is clear. The problem happens when calling
`dbus::Bus::RemoveObjectManager`. Indeed this moves the ObjectManager
out of `dbus::Bus` toward a callback to a new thread. This still works
transitively, because the dbus::Bus owns the thread. The problem happens
after a second transfer back to the original thread.
Indeed, there is a race condition possible:
Behavior without problems: -----------------------------------
┌─────────────┐ ┌───────────┐
│Origin thread│ │DBus thread│
└──────┬──────┘ └─────┬─────┘
RemoveObjectManager() │
│────────────────────────────────>│
│ RemoveObjectManagerInternal()
│<────────────────────────────────│
RemoveObjectManagerInternalHelper() │
~ObjectManager() │
│ ┌─────┴─────┐
Shutdown DBus Thread ─────────────>│DBus thread│
Shutdown DBus Thread <─────────────│DBus thread│
│ └───────────┘
~Bus
┌──────┴──────┐
│Origin thread│
└─────────────┘
Behavior with problems: ----------------------------------------
┌─────────────┐ ┌───────────┐
│Origin thread│ │DBus thread│
└──────┬──────┘ └─────┬─────┘
RemoveObjectManager() │
│────────────────────────────────>│
│ RemoveObjectManagerInternal()
│ ┌────────────│
│ │ ┌─────┴─────┐
Shutdown DBus Thread ─────────────>│DBus thread│
Shutdown DBus Thread <─────────────│DBus thread│
│ │ └───────────┘
~Bus() │
│ │
│<───────────────────┘
RemoveObjectManagerInternalHelper()
~ObjectManager()
┌──────┴──────┐
│Origin thread│
└─────────────┘
-----------------------------------------------------------------
In the second case: ~Bus() is called before ~ObjectManager().
The fix is a use `ObjectManager::Cleanup()` to cleanup the raw_ptr while
the object is still transitively owned by the object it referenced.
Bug: chromium:1478759
Fixed: chromium:1478759
Change-Id: I4ac04d449ab8a7b860256c490f8ac878c1c5c7c5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4839496
Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1192343}
NOKEYCHECK=True
GitOrigin-RevId: bcb156213bee63d24813d2203ef5c36f81785687
diff --git a/object_manager.cc b/object_manager.cc
index 9feaaf9..93e6c87 100644
--- a/object_manager.cc
+++ b/object_manager.cc
@@ -50,11 +50,7 @@
ObjectManager::ObjectManager(Bus* bus,
const std::string& service_name,
const ObjectPath& object_path)
- : bus_(bus),
- service_name_(service_name),
- object_path_(object_path),
- setup_success_(false),
- cleanup_called_(false) {
+ : bus_(bus), service_name_(service_name), object_path_(object_path) {
LOG_IF(FATAL, !object_path_.IsValid()) << object_path_.value();
DVLOG(1) << "Creating ObjectManager for " << service_name_
<< " " << object_path_.value();
@@ -175,15 +171,20 @@
}
match_rule_.clear();
+ // After Cleanup(), the Bus doesn't own `this` anymore and might be deleted
+ // before `this`.
+ bus_ = nullptr;
}
bool ObjectManager::SetupMatchRuleAndFilter() {
- DCHECK(bus_);
DCHECK(!setup_success_);
- bus_->AssertOnDBusThread();
- if (cleanup_called_)
+ if (cleanup_called_) {
return false;
+ }
+
+ DCHECK(bus_);
+ bus_->AssertOnDBusThread();
if (!bus_->Connect() || !bus_->SetUpAsyncOperations())
return false;
@@ -225,16 +226,17 @@
<< ": Failed to set up match rule.";
return;
}
-
- DCHECK(bus_);
DCHECK(object_proxy_);
DCHECK(setup_success_);
- bus_->AssertOnOriginThread();
// |object_proxy_| is no longer valid if the Bus was shut down before this
// call. Don't initiate any other action from the origin thread.
- if (cleanup_called_)
+ if (cleanup_called_) {
return;
+ }
+
+ DCHECK(bus_);
+ bus_->AssertOnOriginThread();
object_proxy_->ConnectToSignal(
kObjectManagerInterface, kObjectManagerInterfacesAdded,
diff --git a/object_manager.h b/object_manager.h
index 824469a..9840aa5 100644
--- a/object_manager.h
+++ b/object_manager.h
@@ -328,14 +328,16 @@
// |service_name_owner_|.
void UpdateServiceNameOwner(const std::string& new_owner);
+ // Valid in between the constructor and `CleanUp()`.
+ // After Cleanup(), `this` lifetime might exceed Bus's one.
raw_ptr<Bus> bus_;
std::string service_name_;
std::string service_name_owner_;
std::string match_rule_;
ObjectPath object_path_;
raw_ptr<ObjectProxy, AcrossTasksDanglingUntriaged> object_proxy_;
- bool setup_success_;
- bool cleanup_called_;
+ bool setup_success_ = false;
+ bool cleanup_called_ = false;
// Maps the name of an interface to the implementation class used for
// instantiating PropertySet structures for that interface's properties.