fix(tray): stop reorderItems from re-adding items (iterator UAF + double add)
The item-ordering feature made Host::reorderItems() re-run the full remove/add path over items_ via std::ranges::for_each(on_remove_/on_add_). This caused two confirmed bugs: BUG 1 (iterator invalidation / UAF): on_add_ (Tray::onAdd) calls Host::checkIgnoreList, which erases from items_ while for_each is still iterating items_, invalidating iterators/pointers. Triggered by a non-empty ignore-list matching an item with >=2 items present. BUG 2 (double add): reorderItems runs while an item's Id is resolved in proxyReady, i.e. before setReady(). It added the not-yet-ready item (re-parenting its event_box, pushing into Tray::items_, connecting signal_show/hide), then setReady() -> itemReady -> onAdd added it again: GTK 'widget already has a parent' critical, duplicate Item* and signal handlers that accumulated unbounded. Fixes: - reorderItems() now only reorders already-added GTK box children via a dedicated on_reorder_ callback (Tray::reorderBox), never re-adding or removing. reorderBox stable-sorts items_ by order_ and repositions children with gtk_box_reorder_child (honouring reverse-direction). - Tray::onAdd is idempotent (guards against an already-added item) and positions the new widget via reorderBox before the ignore-list check. - signal_show/signal_hide connections are stored per item and disconnected in Tray::onRemove; onRemove is a no-op for items that were never added.
This commit is contained in:
@@ -16,7 +16,8 @@ class Host {
|
||||
public:
|
||||
Host(std::size_t id, const Json::Value&, const Bar&, const std::vector<std::string>&,
|
||||
const std::function<void(std::unique_ptr<Item>&)>&,
|
||||
const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&);
|
||||
const std::function<void(std::unique_ptr<Item>&)>&, const std::function<void()>&,
|
||||
const std::function<void()>&);
|
||||
~Host();
|
||||
|
||||
void checkIgnoreList(const std::vector<std::string>& ignore_list,
|
||||
@@ -55,6 +56,10 @@ class Host {
|
||||
const std::vector<std::string> ignore_list_;
|
||||
const std::function<void(std::unique_ptr<Item>&)> on_add_;
|
||||
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
|
||||
// Re-applies the configured ordering to the already-added tray widgets. This
|
||||
// must NOT re-run the add path (which would re-parent widgets and reconnect
|
||||
// signals); it only reorders existing children.
|
||||
const std::function<void()> on_reorder_;
|
||||
|
||||
ItemOrderMap orders_;
|
||||
const std::function<void()> on_update_;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <sigc++/connection.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
@@ -19,6 +23,9 @@ class Tray : public AModule {
|
||||
private:
|
||||
void onAdd(std::unique_ptr<Item>& item);
|
||||
void onRemove(std::unique_ptr<Item>& item);
|
||||
// Reorders the already-added tray widgets by their configured order. Does not
|
||||
// add or remove any widget.
|
||||
void reorderBox();
|
||||
void checkIgnoreList(std::unique_ptr<Item>* item);
|
||||
std::vector<std::string> parseIgnoreList(const Json::Value& config);
|
||||
void queueUpdate();
|
||||
@@ -29,6 +36,9 @@ class Tray : public AModule {
|
||||
std::vector<std::string> ignore_list_;
|
||||
SNI::Host host_;
|
||||
std::vector<Item*> items_;
|
||||
// signal_show/signal_hide connections owned per added item, so they can be
|
||||
// disconnected on removal instead of leaking and accumulating.
|
||||
std::unordered_map<Item*, std::pair<sigc::connection, sigc::connection>> item_connections_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::SNI
|
||||
|
||||
Reference in New Issue
Block a user