Commit 78f22b6a authored by Johannes Berg's avatar Johannes Berg

cfg80211: allow userspace to take ownership of interfaces

When dynamically creating interfaces from userspace, e.g. for P2P usage,
such interfaces are usually owned by the process that created them, i.e.
wpa_supplicant. Should wpa_supplicant crash, such interfaces will often
cease operating properly and cause problems on restarting the process.

To avoid this problem, introduce an ownership concept for interfaces. If
an interface is owned by a netlink socket, then it will be destroyed if
the netlink socket is closed for any reason, including if the process it
belongs to crashed. This gives us a race-free way to get rid of any such
interfaces.
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 6e1ee5d2
...@@ -3194,6 +3194,7 @@ struct cfg80211_cached_keys; ...@@ -3194,6 +3194,7 @@ struct cfg80211_cached_keys;
* @ibss_dfs_possible: (private) IBSS may change to a DFS channel * @ibss_dfs_possible: (private) IBSS may change to a DFS channel
* @event_list: (private) list for internal event processing * @event_list: (private) list for internal event processing
* @event_lock: (private) lock for event list * @event_lock: (private) lock for event list
* @owner_nlportid: (private) owner socket port ID
*/ */
struct wireless_dev { struct wireless_dev {
struct wiphy *wiphy; struct wiphy *wiphy;
...@@ -3241,6 +3242,8 @@ struct wireless_dev { ...@@ -3241,6 +3242,8 @@ struct wireless_dev {
unsigned long cac_start_time; unsigned long cac_start_time;
unsigned int cac_time_ms; unsigned int cac_time_ms;
u32 owner_nlportid;
#ifdef CONFIG_CFG80211_WEXT #ifdef CONFIG_CFG80211_WEXT
/* wext data */ /* wext data */
struct { struct {
......
...@@ -1579,6 +1579,10 @@ enum nl80211_commands { ...@@ -1579,6 +1579,10 @@ enum nl80211_commands {
* @NL80211_ATTR_TDLS_PEER_CAPABILITY: flags for TDLS peer capabilities, u32. * @NL80211_ATTR_TDLS_PEER_CAPABILITY: flags for TDLS peer capabilities, u32.
* As specified in the &enum nl80211_tdls_peer_capability. * As specified in the &enum nl80211_tdls_peer_capability.
* *
* @NL80211_ATTR_IFACE_SOCKET_OWNER: flag attribute, if set during interface
* creation then the new interface will be owned by the netlink socket
* that created it and will be destroyed when the socket is closed
*
* @NL80211_ATTR_MAX: highest attribute number currently defined * @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use * @__NL80211_ATTR_AFTER_LAST: internal use
*/ */
...@@ -1914,6 +1918,8 @@ enum nl80211_attrs { ...@@ -1914,6 +1918,8 @@ enum nl80211_attrs {
NL80211_ATTR_TDLS_PEER_CAPABILITY, NL80211_ATTR_TDLS_PEER_CAPABILITY,
NL80211_ATTR_IFACE_SOCKET_OWNER,
/* add attributes here, update the policy in nl80211.c */ /* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST, __NL80211_ATTR_AFTER_LAST,
......
...@@ -260,6 +260,45 @@ static void cfg80211_event_work(struct work_struct *work) ...@@ -260,6 +260,45 @@ static void cfg80211_event_work(struct work_struct *work)
rtnl_unlock(); rtnl_unlock();
} }
void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev)
{
struct cfg80211_iface_destroy *item;
ASSERT_RTNL();
spin_lock_irq(&rdev->destroy_list_lock);
while ((item = list_first_entry_or_null(&rdev->destroy_list,
struct cfg80211_iface_destroy,
list))) {
struct wireless_dev *wdev, *tmp;
u32 nlportid = item->nlportid;
list_del(&item->list);
kfree(item);
spin_unlock_irq(&rdev->destroy_list_lock);
list_for_each_entry_safe(wdev, tmp, &rdev->wdev_list, list) {
if (nlportid == wdev->owner_nlportid)
rdev_del_virtual_intf(rdev, wdev);
}
spin_lock_irq(&rdev->destroy_list_lock);
}
spin_unlock_irq(&rdev->destroy_list_lock);
}
static void cfg80211_destroy_iface_wk(struct work_struct *work)
{
struct cfg80211_registered_device *rdev;
rdev = container_of(work, struct cfg80211_registered_device,
destroy_work);
rtnl_lock();
cfg80211_destroy_ifaces(rdev);
rtnl_unlock();
}
/* exported functions */ /* exported functions */
struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv)
...@@ -318,6 +357,10 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) ...@@ -318,6 +357,10 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv)
rdev->wiphy.dev.class = &ieee80211_class; rdev->wiphy.dev.class = &ieee80211_class;
rdev->wiphy.dev.platform_data = rdev; rdev->wiphy.dev.platform_data = rdev;
INIT_LIST_HEAD(&rdev->destroy_list);
spin_lock_init(&rdev->destroy_list_lock);
INIT_WORK(&rdev->destroy_work, cfg80211_destroy_iface_wk);
#ifdef CONFIG_CFG80211_DEFAULT_PS #ifdef CONFIG_CFG80211_DEFAULT_PS
rdev->wiphy.flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; rdev->wiphy.flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
#endif #endif
...@@ -675,6 +718,7 @@ void wiphy_unregister(struct wiphy *wiphy) ...@@ -675,6 +718,7 @@ void wiphy_unregister(struct wiphy *wiphy)
cancel_work_sync(&rdev->conn_work); cancel_work_sync(&rdev->conn_work);
flush_work(&rdev->event_work); flush_work(&rdev->event_work);
cancel_delayed_work_sync(&rdev->dfs_update_channels_wk); cancel_delayed_work_sync(&rdev->dfs_update_channels_wk);
flush_work(&rdev->destroy_work);
#ifdef CONFIG_PM #ifdef CONFIG_PM
if (rdev->wiphy.wowlan_config && rdev->ops->set_wakeup) if (rdev->wiphy.wowlan_config && rdev->ops->set_wakeup)
......
...@@ -80,6 +80,10 @@ struct cfg80211_registered_device { ...@@ -80,6 +80,10 @@ struct cfg80211_registered_device {
struct cfg80211_coalesce *coalesce; struct cfg80211_coalesce *coalesce;
spinlock_t destroy_list_lock;
struct list_head destroy_list;
struct work_struct destroy_work;
/* must be last because of the way we do wiphy_priv(), /* must be last because of the way we do wiphy_priv(),
* and it should at least be aligned to NETDEV_ALIGN */ * and it should at least be aligned to NETDEV_ALIGN */
struct wiphy wiphy __aligned(NETDEV_ALIGN); struct wiphy wiphy __aligned(NETDEV_ALIGN);
...@@ -232,6 +236,13 @@ struct cfg80211_beacon_registration { ...@@ -232,6 +236,13 @@ struct cfg80211_beacon_registration {
u32 nlportid; u32 nlportid;
}; };
struct cfg80211_iface_destroy {
struct list_head list;
u32 nlportid;
};
void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev);
/* free object */ /* free object */
void cfg80211_dev_free(struct cfg80211_registered_device *rdev); void cfg80211_dev_free(struct cfg80211_registered_device *rdev);
......
...@@ -385,6 +385,7 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = { ...@@ -385,6 +385,7 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
[NL80211_ATTR_MAC_HINT] = { .len = ETH_ALEN }, [NL80211_ATTR_MAC_HINT] = { .len = ETH_ALEN },
[NL80211_ATTR_WIPHY_FREQ_HINT] = { .type = NLA_U32 }, [NL80211_ATTR_WIPHY_FREQ_HINT] = { .type = NLA_U32 },
[NL80211_ATTR_TDLS_PEER_CAPABILITY] = { .type = NLA_U32 }, [NL80211_ATTR_TDLS_PEER_CAPABILITY] = { .type = NLA_U32 },
[NL80211_ATTR_IFACE_SOCKET_OWNER] = { .type = NLA_FLAG },
}; };
/* policy for the key attributes */ /* policy for the key attributes */
...@@ -2514,6 +2515,9 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) ...@@ -2514,6 +2515,9 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED; enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;
u32 flags; u32 flags;
/* to avoid failing a new interface creation due to pending removal */
cfg80211_destroy_ifaces(rdev);
memset(&params, 0, sizeof(params)); memset(&params, 0, sizeof(params));
if (!info->attrs[NL80211_ATTR_IFNAME]) if (!info->attrs[NL80211_ATTR_IFNAME])
...@@ -2563,6 +2567,9 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) ...@@ -2563,6 +2567,9 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
return PTR_ERR(wdev); return PTR_ERR(wdev);
} }
if (info->attrs[NL80211_ATTR_IFACE_SOCKET_OWNER])
wdev->owner_nlportid = info->snd_portid;
switch (type) { switch (type) {
case NL80211_IFTYPE_MESH_POINT: case NL80211_IFTYPE_MESH_POINT:
if (!info->attrs[NL80211_ATTR_MESH_ID]) if (!info->attrs[NL80211_ATTR_MESH_ID])
...@@ -11649,9 +11656,15 @@ static int nl80211_netlink_notify(struct notifier_block * nb, ...@@ -11649,9 +11656,15 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
rcu_read_lock(); rcu_read_lock();
list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) { list_for_each_entry_rcu(rdev, &cfg80211_rdev_list, list) {
list_for_each_entry_rcu(wdev, &rdev->wdev_list, list) bool schedule_destroy_work = false;
list_for_each_entry_rcu(wdev, &rdev->wdev_list, list) {
cfg80211_mlme_unregister_socket(wdev, notify->portid); cfg80211_mlme_unregister_socket(wdev, notify->portid);
if (wdev->owner_nlportid == notify->portid)
schedule_destroy_work = true;
}
spin_lock_bh(&rdev->beacon_registrations_lock); spin_lock_bh(&rdev->beacon_registrations_lock);
list_for_each_entry_safe(reg, tmp, &rdev->beacon_registrations, list_for_each_entry_safe(reg, tmp, &rdev->beacon_registrations,
list) { list) {
...@@ -11662,6 +11675,19 @@ static int nl80211_netlink_notify(struct notifier_block * nb, ...@@ -11662,6 +11675,19 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
} }
} }
spin_unlock_bh(&rdev->beacon_registrations_lock); spin_unlock_bh(&rdev->beacon_registrations_lock);
if (schedule_destroy_work) {
struct cfg80211_iface_destroy *destroy;
destroy = kzalloc(sizeof(*destroy), GFP_ATOMIC);
if (destroy) {
destroy->nlportid = notify->portid;
spin_lock(&rdev->destroy_list_lock);
list_add(&destroy->list, &rdev->destroy_list);
spin_unlock(&rdev->destroy_list_lock);
schedule_work(&rdev->destroy_work);
}
}
} }
rcu_read_unlock(); rcu_read_unlock();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment