From: Wei Wang Add a new netdev event for dev unregister and handle the removal of this dev from psp->assoc_dev_list, upon the first dev-assoc operation. Signed-off-by: Wei Wang Reviewed-by: Daniel Zahka --- Documentation/netlink/specs/psp.yaml | 2 +- net/psp/psp-nl-gen.c | 2 +- net/psp/psp-nl-gen.h | 3 ++ net/psp/psp.h | 1 + net/psp/psp_main.c | 76 ++++++++++++++++++++++++++++ net/psp/psp_nl.c | 29 +++++++++++ 6 files changed, 111 insertions(+), 2 deletions(-) diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml index 3d1b7223e084..538ed9184965 100644 --- a/Documentation/netlink/specs/psp.yaml +++ b/Documentation/netlink/specs/psp.yaml @@ -328,7 +328,7 @@ operations: - nsid reply: attributes: [] - pre: psp-device-get-locked + pre: psp-device-get-locked-dev-assoc post: psp-device-unlock - name: dev-disassoc diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c index 114299c64423..389a8480cc3d 100644 --- a/net/psp/psp-nl-gen.c +++ b/net/psp/psp-nl-gen.c @@ -135,7 +135,7 @@ static const struct genl_split_ops psp_nl_ops[] = { }, { .cmd = PSP_CMD_DEV_ASSOC, - .pre_doit = psp_device_get_locked, + .pre_doit = psp_device_get_locked_dev_assoc, .doit = psp_nl_dev_assoc_doit, .post_doit = psp_device_unlock, .policy = psp_dev_assoc_nl_policy, diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h index 4dd0f0f23053..24d51bff997f 100644 --- a/net/psp/psp-nl-gen.h +++ b/net/psp/psp-nl-gen.h @@ -21,6 +21,9 @@ int psp_device_get_locked_admin(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); int psp_assoc_device_get_locked(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +int psp_device_get_locked_dev_assoc(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); void psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); diff --git a/net/psp/psp.h b/net/psp/psp.h index 0f9c4e4e52cb..c82b21bae240 100644 --- a/net/psp/psp.h +++ b/net/psp/psp.h @@ -15,6 +15,7 @@ extern struct mutex psp_devs_lock; void psp_dev_free(struct psp_dev *psd); int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin); +int psp_attach_netdev_notifier(void); void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd); diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c index 792a47105a6c..a57d30db22be 100644 --- a/net/psp/psp_main.c +++ b/net/psp/psp_main.c @@ -391,6 +391,82 @@ int psp_dev_rcv(struct sk_buff *skb, u16 dev_id, u8 generation, bool strip_icv) } EXPORT_SYMBOL(psp_dev_rcv); +static void psp_dev_disassoc_one(struct psp_dev *psd, struct net_device *dev) +{ + struct psp_assoc_dev *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, &psd->assoc_dev_list, dev_list) { + if (entry->assoc_dev == dev) { + list_del(&entry->dev_list); + psd->assoc_dev_cnt--; + rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL); + netdev_put(entry->assoc_dev, &entry->dev_tracker); + kfree(entry); + return; + } + } +} + +static int psp_netdev_event(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + struct psp_dev *psd; + + if (event != NETDEV_UNREGISTER) + return NOTIFY_DONE; + + rcu_read_lock(); + psd = rcu_dereference(dev->psp_dev); + if (psd && psp_dev_tryget(psd)) { + rcu_read_unlock(); + mutex_lock(&psd->lock); + psp_dev_disassoc_one(psd, dev); + mutex_unlock(&psd->lock); + psp_dev_put(psd); + } else { + rcu_read_unlock(); + } + + return NOTIFY_DONE; +} + +static struct notifier_block psp_netdev_notifier = { + .notifier_call = psp_netdev_event, +}; + +static DEFINE_MUTEX(psp_notifier_lock); +static bool psp_notifier_registered; + +/** + * psp_attach_netdev_notifier() - register netdev notifier on first use + * + * Register the netdevice notifier when the first device association + * is created. In many installations no associations will be created and + * the notifier won't be needed. + * + * Must be called without psd->lock held, due to lock ordering: + * rtnl_lock -> psd->lock (the notifier callback runs under rtnl_lock + * and takes psd->lock). + */ +int psp_attach_netdev_notifier(void) +{ + int err = 0; + + if (READ_ONCE(psp_notifier_registered)) + return 0; + + mutex_lock(&psp_notifier_lock); + if (!psp_notifier_registered) { + err = register_netdevice_notifier(&psp_netdev_notifier); + if (!err) + WRITE_ONCE(psp_notifier_registered, true); + } + mutex_unlock(&psp_notifier_lock); + + return err; +} + static int __init psp_init(void) { mutex_init(&psp_devs_lock); diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index 75ca32821d28..d622f91a979e 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -167,6 +167,22 @@ int psp_device_get_locked(const struct genl_split_ops *ops, return __psp_device_get_locked(ops, skb, info, false); } +/* + * Non-admin version of psp_device_get_locked() + psp_attach_netdev_notifier() + * only used for dev-assoc. + */ +int psp_device_get_locked_dev_assoc(const struct genl_split_ops *ops, + struct sk_buff *skb, struct genl_info *info) +{ + int err; + + err = psp_attach_netdev_notifier(); + if (err) + return err; + + return __psp_device_get_locked(ops, skb, info, false); +} + static struct net *psp_nl_resolve_assoc_dev_ns(struct psp_dev *psd, struct genl_info *info) { @@ -532,6 +548,19 @@ int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info) } psp_assoc_dev->assoc_dev = assoc_dev; + + /* Check for race with NETDEV_UNREGISTER. The cmpxchg above is a + * full barrier, and the unregister path has synchronize_net() + * between setting NETREG_UNREGISTERING and reading psp_dev in the + * notifier. So at least one side would do the clean-up if we are in + * the middle of unregitering assoc_dev. + * And the clean-up is serialized by psd->lock. + */ + if (READ_ONCE(assoc_dev->reg_state) != NETREG_REGISTERED) { + err = -ENODEV; + goto rsp_err; + } + rsp = psp_nl_reply_new(info); if (!rsp) { err = -ENOMEM; -- 2.52.0