diff options
author | Thomas Haller <thaller@redhat.com> | 2016-01-19 15:42:24 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2016-01-21 14:58:55 +0100 |
commit | f1fba3eb02c5d102a1b0e85c371dce81e5bd0d3b (patch) | |
tree | b8517cc1c9493671410c98f4652e4042e83eb7b9 | |
parent | 063f9185b95336c269c2b32484e2f6aeaae85d11 (diff) | |
download | NetworkManager-f1fba3eb02c5d102a1b0e85c371dce81e5bd0d3b.tar.gz |
wifi: fix crash due to wrong ownership handling in nm_supplicant_manager_iface_release()
nm_supplicant_manager_iface_get() would cache and reuse the supplicant
interface. But no ref-counting was in place so that the first user returning
the interface via nm_supplicant_manager_iface_release() would destroy the
instance for others.
This is broken for a very long time. Which shows that we hardly ever
have a cache-hit and usually create a new instance. So, instead of
letting nm_supplicant_manager_create_interface() check for existing
supplicant interface, always create a new instance. This also makes
sense, because we would expect that per ifname only one instance is
requested at a time. Also add an assertion that we don't return
multiple supplicant interface instances for the same ifname.
Drop nm_supplicant_manager_iface_release() in favor of requiring users
to unref the returned instance.
Also, use a GSList instead of a GHashTable for the cache.
Also, previously callers would pass @is_wireless to nm_supplicant_manager_iface_get(),
but the cache lookup did not consider that value. That doesn't matter
now as we always create a new instance.
https://bugzilla.redhat.com/show_bug.cgi?id=1298007
-rw-r--r-- | src/devices/nm-device-ethernet.c | 16 | ||||
-rw-r--r-- | src/devices/wifi/nm-device-wifi.c | 16 | ||||
-rw-r--r-- | src/supplicant-manager/nm-supplicant-manager.c | 159 | ||||
-rw-r--r-- | src/supplicant-manager/nm-supplicant-manager.h | 9 |
4 files changed, 113 insertions, 87 deletions
diff --git a/src/devices/nm-device-ethernet.c b/src/devices/nm-device-ethernet.c index 7ff6d685f3..91fd0f7396 100644 --- a/src/devices/nm-device-ethernet.c +++ b/src/devices/nm-device-ethernet.c @@ -474,8 +474,7 @@ supplicant_interface_release (NMDeviceEthernet *self) if (priv->supplicant.iface) { nm_supplicant_interface_disconnect (priv->supplicant.iface); - nm_supplicant_manager_iface_release (priv->supplicant.mgr, priv->supplicant.iface); - priv->supplicant.iface = NULL; + g_clear_object (&priv->supplicant.iface); } } @@ -784,14 +783,15 @@ supplicant_interface_init (NMDeviceEthernet *self) { NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); - /* Create supplicant interface */ - priv->supplicant.iface = nm_supplicant_manager_iface_get (priv->supplicant.mgr, - nm_device_get_iface (NM_DEVICE (self)), - FALSE); + supplicant_interface_release (self); + + priv->supplicant.iface = nm_supplicant_manager_create_interface (priv->supplicant.mgr, + nm_device_get_iface (NM_DEVICE (self)), + FALSE); + if (!priv->supplicant.iface) { _LOGE (LOGD_DEVICE | LOGD_ETHER, "Couldn't initialize supplicant interface"); - supplicant_interface_release (self); return FALSE; } @@ -1633,6 +1633,8 @@ dispose (GObject *object) NMDeviceEthernet *self = NM_DEVICE_ETHERNET (object); NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE (self); + supplicant_interface_release (self); + nm_clear_g_source (&priv->pppoe_wait_id); dcb_timeout_cleanup (NM_DEVICE (self)); diff --git a/src/devices/wifi/nm-device-wifi.c b/src/devices/wifi/nm-device-wifi.c index dbe6f33fb2..2df0937725 100644 --- a/src/devices/wifi/nm-device-wifi.c +++ b/src/devices/wifi/nm-device-wifi.c @@ -192,13 +192,12 @@ supplicant_interface_acquire (NMDeviceWifi *self) NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (self); g_return_val_if_fail (self != NULL, FALSE); - /* interface already acquired? */ - g_return_val_if_fail (priv->sup_iface == NULL, TRUE); + g_return_val_if_fail (!priv->sup_iface, TRUE); - priv->sup_iface = nm_supplicant_manager_iface_get (priv->sup_mgr, - nm_device_get_iface (NM_DEVICE (self)), - TRUE); - if (priv->sup_iface == NULL) { + priv->sup_iface = nm_supplicant_manager_create_interface (priv->sup_mgr, + nm_device_get_iface (NM_DEVICE (self)), + TRUE); + if (!priv->sup_iface) { _LOGE (LOGD_WIFI, "Couldn't initialize supplicant interface"); return FALSE; } @@ -263,8 +262,7 @@ supplicant_interface_release (NMDeviceWifi *self) /* Tell the supplicant to disconnect from the current AP */ nm_supplicant_interface_disconnect (priv->sup_iface); - nm_supplicant_manager_iface_release (priv->sup_mgr, priv->sup_iface); - priv->sup_iface = NULL; + g_clear_object (&priv->sup_iface); } } @@ -2195,7 +2193,7 @@ build_supplicant_config (NMDeviceWifi *self, NMSettingMacRandomization mac_randomization_fallback; gs_free char *svalue = NULL; - g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (priv->sup_iface, NULL); s_wireless = nm_connection_get_setting_wireless (connection); g_return_val_if_fail (s_wireless != NULL, NULL); diff --git a/src/supplicant-manager/nm-supplicant-manager.c b/src/supplicant-manager/nm-supplicant-manager.c index ac2b1fb867..14254266a3 100644 --- a/src/supplicant-manager/nm-supplicant-manager.c +++ b/src/supplicant-manager/nm-supplicant-manager.c @@ -40,7 +40,7 @@ typedef struct { GCancellable * cancellable; gboolean running; - GHashTable * ifaces; + GSList *ifaces; gboolean fast_supported; NMSupplicantFeature ap_support; guint die_count_reset_id; @@ -70,64 +70,29 @@ is_available (NMSupplicantManager *self) /********************************************************************/ -NMSupplicantInterface * -nm_supplicant_manager_iface_get (NMSupplicantManager * self, - const char *ifname, - gboolean is_wireless) -{ - NMSupplicantManagerPrivate *priv; - NMSupplicantInterface *iface = NULL; - - g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL); - g_return_val_if_fail (ifname != NULL, NULL); - - priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - - iface = g_hash_table_lookup (priv->ifaces, ifname); - if (!iface) { - nm_log_dbg (LOGD_SUPPLICANT, "(%s): creating new supplicant interface", ifname); - iface = nm_supplicant_interface_new (ifname, - is_wireless, - priv->fast_supported, - priv->ap_support); - g_hash_table_insert (priv->ifaces, - (char *) nm_supplicant_interface_get_ifname (iface), - iface); - - /* If we're making the supplicant take a time out for a bit, don't - * let the supplicant interface start immediately, just let it hang - * around in INIT state until we're ready to talk to the supplicant - * again. - */ - if (is_available (self)) - nm_supplicant_interface_set_supplicant_available (iface, TRUE); - } else { - nm_log_dbg (LOGD_SUPPLICANT, "(%s): returning existing supplicant interface", ifname); - } - - return iface; -} - -void -nm_supplicant_manager_iface_release (NMSupplicantManager *self, - NMSupplicantInterface *iface) +static void +_sup_iface_last_ref (gpointer data, + GObject *object, + gboolean is_last_ref) { + NMSupplicantManager *self = data; NMSupplicantManagerPrivate *priv; - const char *ifname, *op; + NMSupplicantInterface *sup_iface = (NMSupplicantInterface *) object; + const char *op; g_return_if_fail (NM_IS_SUPPLICANT_MANAGER (self)); - g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (iface)); - - ifname = nm_supplicant_interface_get_ifname (iface); - g_assert (ifname); + g_return_if_fail (NM_IS_SUPPLICANT_INTERFACE (sup_iface)); + g_return_if_fail (is_last_ref); priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - g_return_if_fail (g_hash_table_lookup (priv->ifaces, ifname) == iface); + if (!g_slist_find (priv->ifaces, sup_iface)) + g_return_if_reached (); /* Ask wpa_supplicant to remove this interface */ - op = nm_supplicant_interface_get_object_path (iface); - if (priv->running && priv->proxy && op) { + if ( priv->running + && priv->proxy + && (op = nm_supplicant_interface_get_object_path (sup_iface))) { g_dbus_proxy_call (priv->proxy, "RemoveInterface", g_variant_new ("(o)", op), @@ -138,15 +103,69 @@ nm_supplicant_manager_iface_release (NMSupplicantManager *self, NULL); } - g_hash_table_remove (priv->ifaces, ifname); + priv->ifaces = g_slist_remove (priv->ifaces, sup_iface); + g_object_remove_toggle_ref ((GObject *) sup_iface, _sup_iface_last_ref, self); +} + +/** + * nm_supplicant_manager_create_interface: + * @self: the #NMSupplicantManager + * @ifname: the interface for which to obtain the supplicant interface + * @is_wireless: whether the interface is supposed to be wireless. + * + * Note: the manager owns a reference to the instance and the only way to + * get the manager to release it, is by dropping all other references + * to the supplicant-interface (or destroying the manager). + * + * Retruns: (transfer-full): returns a #NMSupplicantInterface or %NULL. + * Must be unrefed at the end. + * */ +NMSupplicantInterface * +nm_supplicant_manager_create_interface (NMSupplicantManager *self, + const char *ifname, + gboolean is_wireless) +{ + NMSupplicantManagerPrivate *priv; + NMSupplicantInterface *iface; + GSList *ifaces; + + g_return_val_if_fail (NM_IS_SUPPLICANT_MANAGER (self), NULL); + g_return_val_if_fail (ifname != NULL, NULL); + + priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + + nm_log_dbg (LOGD_SUPPLICANT, "(%s): creating new supplicant interface", ifname); + + /* assert against not requesting duplicate interfaces. */ + for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) { + if (g_strcmp0 (nm_supplicant_interface_get_ifname (ifaces->data), ifname) == 0) + g_return_val_if_reached (NULL); + } + + iface = nm_supplicant_interface_new (ifname, + is_wireless, + priv->fast_supported, + priv->ap_support); + + priv->ifaces = g_slist_prepend (priv->ifaces, iface); + g_object_add_toggle_ref ((GObject *) iface, _sup_iface_last_ref, self); + + /* If we're making the supplicant take a time out for a bit, don't + * let the supplicant interface start immediately, just let it hang + * around in INIT state until we're ready to talk to the supplicant + * again. + */ + if (is_available (self)) + nm_supplicant_interface_set_supplicant_available (iface, TRUE); + + return iface; } static void update_capabilities (NMSupplicantManager *self) { NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - NMSupplicantInterface *iface; - GHashTableIter hash_iter; + GSList *ifaces; const char **array; GVariant *value; @@ -174,9 +193,8 @@ update_capabilities (NMSupplicantManager *self) } /* Tell all interfaces about results of the AP check */ - g_hash_table_iter_init (&hash_iter, priv->ifaces); - while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &iface)) - nm_supplicant_interface_set_ap_support (iface, priv->ap_support); + for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) + nm_supplicant_interface_set_ap_support (ifaces->data, priv->ap_support); nm_log_dbg (LOGD_SUPPLICANT, "AP mode is %ssupported", (priv->ap_support == NM_SUPPLICANT_FEATURE_YES) ? "" : @@ -202,15 +220,20 @@ static void availability_changed (NMSupplicantManager *self, gboolean available) { NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - GList *ifaces, *iter; + GSList *ifaces, *iter; - /* priv->ifaces may be modified if availability changes; can't use GHashTableIter */ - ifaces = g_hash_table_get_values (priv->ifaces); + if (!priv->ifaces) + return; + + /* setting the supplicant as unavailable might cause the caller to unref + * the supplicant (and thus remove the instance from the list of interfaces. + * Delay that by taking an additional reference first. */ + ifaces = g_slist_copy (priv->ifaces); for (iter = ifaces; iter; iter = iter->next) g_object_ref (iter->data); for (iter = ifaces; iter; iter = iter->next) - nm_supplicant_interface_set_supplicant_available (NM_SUPPLICANT_INTERFACE (iter->data), available); - g_list_free_full (ifaces, g_object_unref); + nm_supplicant_interface_set_supplicant_available (iter->data, available); + g_slist_free_full (ifaces, g_object_unref); } static void @@ -326,8 +349,6 @@ nm_supplicant_manager_init (NMSupplicantManager *self) { NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); - priv->ifaces = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); - priv->cancellable = g_cancellable_new (); g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, @@ -343,7 +364,9 @@ nm_supplicant_manager_init (NMSupplicantManager *self) static void dispose (GObject *object) { - NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (object); + NMSupplicantManager *self = (NMSupplicantManager *) object; + NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE (self); + GSList *ifaces; nm_clear_g_source (&priv->die_count_reset_id); @@ -352,7 +375,13 @@ dispose (GObject *object) g_clear_object (&priv->cancellable); } - g_clear_pointer (&priv->ifaces, g_hash_table_unref); + if (priv->ifaces) { + for (ifaces = priv->ifaces; ifaces; ifaces = ifaces->next) + g_object_remove_toggle_ref (ifaces->data, _sup_iface_last_ref, self); + g_slist_free (priv->ifaces); + priv->ifaces = NULL; + } + g_clear_object (&priv->proxy); G_OBJECT_CLASS (nm_supplicant_manager_parent_class)->dispose (object); diff --git a/src/supplicant-manager/nm-supplicant-manager.h b/src/supplicant-manager/nm-supplicant-manager.h index d7456467de..b0ce6a523f 100644 --- a/src/supplicant-manager/nm-supplicant-manager.h +++ b/src/supplicant-manager/nm-supplicant-manager.h @@ -49,11 +49,8 @@ GType nm_supplicant_manager_get_type (void); NMSupplicantManager *nm_supplicant_manager_get (void); -NMSupplicantInterface *nm_supplicant_manager_iface_get (NMSupplicantManager *mgr, - const char *ifname, - gboolean is_wireless); - -void nm_supplicant_manager_iface_release (NMSupplicantManager *mgr, - NMSupplicantInterface *iface); +NMSupplicantInterface *nm_supplicant_manager_create_interface (NMSupplicantManager *mgr, + const char *ifname, + gboolean is_wireless); #endif /* __NETWORKMANAGER_SUPPLICANT_MANAGER_H__ */ |