This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch master
in repository enlightenment.
View the commit online.
commit 8d02842ae1ad1d4e0993b51ef6ecfc33bdabcce9
Author: [email protected] <[email protected]>
AuthorDate: Fri Mar 13 15:49:43 2026 -0600
fix: resolve use-after-free and race conditions in NetworkManager forget button
The forget button implementation had multiple concurrent issues that are now
fixed together:
Use-after-free (root cause): When a user clicked the forget button, it called
enm_mod_aps_update_now() to immediately rebuild the UI, which deleted the
forget button widget and triggered EVAS_CALLBACK_DEL. This callback freed the
_Enm_Forget_Data structure including the connection_path stringshare before
enm_connection_delete() could use it. Fixed by saving and ref'ing the path and
nm pointer before the update, then using them after.
Optimistic UI feedback: The forget button now disappears immediately upon click
by removing the SSID from the saved_connections hash and rebuilding popups,
rather than waiting for D-Bus round trips. This provides instant visual
feedback to the user.
Race window on delete: After Connection.Delete() succeeds, NM may not have
immediately removed the connection from ListConnections(), causing subsequent
polls to re-add it to the hash. Fixed by subscribing to the authoritative
org.freedesktop.NetworkManager.Settings.ConnectionRemoved signal, which fires
once NM's internal state is updated.
Failure recovery: If Connection.Delete() fails, we now call
enm_saved_connections_get() to restore the optimistically-removed entry so the
button reappears, informing the user of the failure.
Signal handler data consistency: _settings_conn_removed_cb now correctly uses
the provided data parameter (NM_Manager *) instead of relying on the global
nm_manager variable.
Fixes #unknown (ConnectionRemoved signal handling)
---
src/modules/networkmanager/e_mod_main.c | 48 +++++++++++++++++++++++-
src/modules/networkmanager/e_mod_main.h | 1 +
src/modules/networkmanager/e_networkmanager.c | 54 +++++++++++++++++++++++++--
src/modules/networkmanager/e_networkmanager.h | 6 +++
4 files changed, 104 insertions(+), 5 deletions(-)
diff --git a/src/modules/networkmanager/e_mod_main.c b/src/modules/networkmanager/e_mod_main.c
index 35d1f49f3..46b3ec340 100644
--- a/src/modules/networkmanager/e_mod_main.c
+++ b/src/modules/networkmanager/e_mod_main.c
@@ -182,6 +182,7 @@ struct _Enm_Forget_Data
{
struct NM_Manager *nm;
const char *connection_path; /* stringshare */
+ const char *ssid; /* stringshare — for optimistic hash removal */
};
static void
@@ -190,9 +191,29 @@ _enm_forget_mouse_up_cb(void *data, Evas *e EINA_UNUSED,
void *event_info EINA_UNUSED)
{
struct _Enm_Forget_Data *fd = data;
+ struct NM_Manager *nm = fd->nm;
+ /* Take a ref now: enm_mod_aps_update_now() may delete the forget button
+ * widget (replacing it with NULL), which fires EVAS_CALLBACK_DEL and frees
+ * fd — including its connection_path stringshare — before we use it. */
+ const char *conn_path = eina_stringshare_ref(fd->connection_path);
- INF("Forget connection: %s", fd->connection_path);
- enm_connection_delete(fd->nm, fd->connection_path);
+ INF("Forget connection: %s (ssid=%s)", conn_path, fd->ssid ?: "(null)");
+
+ /* Optimistic update: remove SSID from local hash immediately so the
+ * forget icon disappears at once without waiting for D-Bus round trips.
+ * The authoritative refresh happens later via the ConnectionRemoved signal. */
+ if (nm->saved_connections && fd->ssid)
+ eina_hash_del_by_key(nm->saved_connections, fd->ssid);
+
+ /* Rebuild all open popups right now, bypassing the 0.5s throttle timer.
+ * This deletes the forget button widget, which frees fd — do not touch
+ * fd after this point. */
+ enm_mod_aps_update_now();
+
+ /* Kick off the actual async delete — ConnectionRemoved signal will
+ * trigger enm_saved_connections_get once NM has committed the removal. */
+ enm_connection_delete(nm, conn_path);
+ eina_stringshare_del(conn_path);
}
static void
@@ -202,6 +223,7 @@ _enm_forget_data_free_cb(void *data, Evas *e EINA_UNUSED,
{
struct _Enm_Forget_Data *fd = data;
eina_stringshare_del(fd->connection_path);
+ eina_stringshare_del(fd->ssid);
free(fd);
}
@@ -256,6 +278,7 @@ _enm_ap_end_new(struct NM_Manager *nm, struct NM_Access_Point *ap, Evas *evas)
evas_object_del(end);
return NULL;
}
+ fd->ssid = eina_stringshare_add(ap->ssid);
evas_object_propagate_events_set(end, EINA_FALSE);
evas_object_event_callback_add(end, EVAS_CALLBACK_MOUSE_UP,
@@ -609,6 +632,27 @@ enm_mod_aps_changed(struct NM_Manager *nm EINA_UNUSED)
ctxt);
}
+void
+enm_mod_aps_update_now(void)
+{
+ E_NM_Module_Context *ctxt;
+ if (!networkmanager_mod) return;
+ ctxt = networkmanager_mod->data;
+ const Eina_List *l;
+ E_NM_Instance *inst;
+
+ /* Cancel any pending throttle timer — we are doing an immediate rebuild */
+ E_FREE_FUNC(ctxt->popup_update_timer, ecore_timer_del);
+
+ if (!ctxt->nm) return;
+
+ EINA_LIST_FOREACH(ctxt->instances, l, inst)
+ {
+ if (!inst->popup) continue;
+ _enm_popup_update(ctxt->nm, inst);
+ }
+}
+
static void
_enm_gadget_setup(E_NM_Instance *inst)
{
diff --git a/src/modules/networkmanager/e_mod_main.h b/src/modules/networkmanager/e_mod_main.h
index 826fff3d6..6231461cf 100644
--- a/src/modules/networkmanager/e_mod_main.h
+++ b/src/modules/networkmanager/e_mod_main.h
@@ -61,6 +61,7 @@ E_API int e_modapi_shutdown(E_Module *m);
E_API int e_modapi_save(E_Module *m);
void enm_popup_del(E_NM_Instance *inst);
+void enm_mod_aps_update_now(void);
const char *e_nm_theme_path(void);
E_NM_Agent *enm_agent_new(Eldbus_Connection *eldbus_conn) EINA_ARG_NONNULL(1);
diff --git a/src/modules/networkmanager/e_networkmanager.c b/src/modules/networkmanager/e_networkmanager.c
index 404c9f4f8..a98343a52 100644
--- a/src/modules/networkmanager/e_networkmanager.c
+++ b/src/modules/networkmanager/e_networkmanager.c
@@ -1078,6 +1078,7 @@ _manager_probe_active_conn(struct NM_Manager *nm,
static void _manager_free(struct NM_Manager *nm);
static struct NM_Manager *_manager_new(void);
+static void _settings_conn_removed_cb(void *data, const Eldbus_Message *msg);
static void
_manager_prop_changed(void *data, const Eldbus_Message *msg)
@@ -1511,12 +1512,21 @@ _connection_delete_cb(void *data, const Eldbus_Message *msg,
{
ERR("Failed to delete connection %s: %s %s",
ctx->path, err_name, err_msg);
+ /* Recover: the optimistic UI removal already hid the forget button.
+ * Refresh saved_connections so the button reappears if the delete
+ * failed (e.g. stale connection path). */
+ enm_saved_connections_get(ctx->nm);
goto done;
}
- INF("Connection %s deleted successfully", ctx->path);
- /* Refresh saved connections to update forget button visibility */
- enm_saved_connections_get(ctx->nm);
+ INF("Connection %s deleted successfully — awaiting ConnectionRemoved signal",
+ ctx->path);
+ /* Do NOT call enm_saved_connections_get here: NM may not have removed the
+ * connection from its internal list yet, so ListConnections would still
+ * return the deleted path and the SSID would be re-added to the hash.
+ * The ConnectionRemoved signal (_settings_conn_removed_cb) fires once NM
+ * has actually removed the object and will trigger the authoritative
+ * refresh. */
done:
eldbus_proxy_unref(ctx->proxy);
@@ -1546,6 +1556,17 @@ enm_connection_delete(struct NM_Manager *nm, const char *connection_path)
/* ctx, proxy, and obj are freed/unref'd inside _connection_delete_cb */
}
+/* Called when NM removes a saved connection — refresh the hash so the
+ * forget button disappears without a visible race window. */
+static void
+_settings_conn_removed_cb(void *data, const Eldbus_Message *msg EINA_UNUSED)
+{
+ struct NM_Manager *nm = data;
+ if (!nm) return;
+ INF("ConnectionRemoved signal: refreshing saved connections");
+ enm_saved_connections_get(nm);
+}
+
static struct NM_Manager *
_manager_new(void)
{
@@ -1578,6 +1599,16 @@ _manager_new(void)
eldbus_proxy_call(nm->proxy, "GetDevices",
_manager_get_devices_cb, nm, -1, "");
+ /* Persistent Settings object for ConnectionRemoved signal.
+ * This avoids the race in _connection_delete_cb where ListConnections
+ * may still return the just-deleted path before NM's internal list is
+ * updated. Instead we react to the authoritative signal. */
+ nm->settings_obj = eldbus_object_get(conn, NM_BUS_NAME, NM_SETTINGS_PATH);
+ nm->settings_proxy = eldbus_proxy_get(nm->settings_obj, NM_IFACE_SETTINGS);
+ nm->conn_removed_handler =
+ eldbus_proxy_signal_handler_add(nm->settings_proxy, "ConnectionRemoved",
+ _settings_conn_removed_cb, nm);
+
return nm;
}
@@ -1614,6 +1645,23 @@ _manager_free(struct NM_Manager *nm)
eina_stringshare_del(nm->active_ap_path);
eina_stringshare_del(nm->active_connection_path);
+ /* Tear down the Settings signal subscription */
+ if (nm->conn_removed_handler)
+ {
+ eldbus_signal_handler_del(nm->conn_removed_handler);
+ nm->conn_removed_handler = NULL;
+ }
+ if (nm->settings_proxy)
+ {
+ eldbus_proxy_unref(nm->settings_proxy);
+ nm->settings_proxy = NULL;
+ }
+ if (nm->settings_obj)
+ {
+ eldbus_object_unref(nm->settings_obj);
+ nm->settings_obj = NULL;
+ }
+
obj = eldbus_proxy_object_get(nm->proxy);
eldbus_proxy_unref(nm->proxy);
eldbus_proxy_unref(nm->props_proxy);
diff --git a/src/modules/networkmanager/e_networkmanager.h b/src/modules/networkmanager/e_networkmanager.h
index 152378184..b128f1abd 100644
--- a/src/modules/networkmanager/e_networkmanager.h
+++ b/src/modules/networkmanager/e_networkmanager.h
@@ -130,6 +130,12 @@ struct NM_Manager
Eina_Hash *saved_connections;
int saved_conn_pending; /* outstanding GetSettings calls */
+ /* Long-lived Settings object/proxy for ConnectionRemoved signal subscription.
+ * Created in _manager_new, freed in _manager_free. */
+ Eldbus_Proxy *settings_proxy;
+ Eldbus_Object *settings_obj;
+ Eldbus_Signal_Handler *conn_removed_handler;
+
/* Generation counter incremented each time a new batch of active-connection
* probes is started. Each probe captures the generation at creation time
* and discards its result in the callback if the generation has advanced,
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.