From 5181f84648c20742e2a73146387b887c3ad1a3eb Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:39:36 +0200 Subject: [PATCH] fix(sni): prevent use-after-free and infinite retry in Host proxy retry Store the retry timeout connection in a Host member and disconnect it in ~Host, so a Host destroyed within the retry window no longer invokes the timeout callback on freed memory. Cap the retries (MAX_RETRIES) so a watcher that never reappears no longer spins forever every 200ms; reset the counter on success and when the watcher name vanishes. --- include/modules/sni/host.hpp | 2 ++ src/modules/sni/host.cpp | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/include/modules/sni/host.hpp b/include/modules/sni/host.hpp index d76ec74a..f18cf414 100644 --- a/include/modules/sni/host.hpp +++ b/include/modules/sni/host.hpp @@ -43,6 +43,8 @@ class Host { std::size_t watcher_id_; GCancellable* cancellable_ = nullptr; SnWatcher* watcher_ = nullptr; + sigc::connection retry_connection_; + unsigned retry_count_ = 0; const Json::Value& config_; const Bar& bar_; const std::function&)> on_add_; diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 091c45d5..2c0c42c7 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -7,6 +7,7 @@ namespace waybar::modules::SNI { static const unsigned RETRY_DELAY_MS = 200; +static const unsigned MAX_RETRIES = 10; Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, const std::function&)>& on_add, @@ -24,6 +25,7 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, on_update_(on_update) {} Host::~Host() { + retry_connection_.disconnect(); if (bus_name_id_ > 0) { Gio::DBus::unown_name(bus_name_id_); bus_name_id_ = 0; @@ -55,6 +57,8 @@ void Host::nameAppeared(const Glib::RefPtr& conn, const G } void Host::nameVanished(const Glib::RefPtr& conn, const Glib::ustring name) { + retry_connection_.disconnect(); + retry_count_ = 0; g_cancellable_cancel(cancellable_); g_clear_object(&cancellable_); g_clear_object(&watcher_); @@ -77,17 +81,26 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { if (error != nullptr) { spdlog::error("Host: {}", error->message); g_clear_object(&host->cancellable_); - Glib::signal_timeout().connect_once( + if (host->retry_count_ >= MAX_RETRIES) { + spdlog::warn("Host: giving up on watcher proxy creation after {} retries", host->retry_count_); + return; + } + host->retry_count_ += 1; + // Store the timeout connection so it is disconnected in ~Host, avoiding a + // use-after-free if the Host is destroyed before the retry fires. + host->retry_connection_ = Glib::signal_timeout().connect( [host]() { if (host->watcher_ != nullptr) { - return; + return false; } auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION); host->nameAppeared(conn, "org.kde.StatusNotifierWatcher", ""); + return false; }, RETRY_DELAY_MS); return; } + host->retry_count_ = 0; host->watcher_ = watcher; sn_watcher_call_register_host(host->watcher_, host->object_path_.c_str(), host->cancellable_, &Host::registerHost, data);