A tc_action_ops structure is visibile as soon as it is placed in the act_base list. When tcf_regsiter_action adds an item to this list and drops act_mod_lock, registration is not complete until register_pernet_subsys() finishes.
If two threads attempt to modify a tc action in a way that triggers a module load, the thread that wins the race ends up defeferencing a NULL pointer after tcf_action_init_1() invokes a_o->init(). In the particular case that this submitter encountered, the panic occurred in tcf_gact_init() when net_generic() returned a NULL tc_action_net pointer. The gact_net_id needed to fetch the correct pointer was not yet set, because the register_pernet_subsys() call was pending in another thread. Fixes: ddf97ccdd7cb ("net_sched: add network namespace support for tc actions") Signed-off-by: Krister Johansen <k...@templeofstupid.com> --- include/net/act_api.h | 1 + net/sched/act_api.c | 64 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/include/net/act_api.h b/include/net/act_api.h index 82f3c91..9ede72d 100644 --- a/include/net/act_api.h +++ b/include/net/act_api.h @@ -107,6 +107,7 @@ struct tc_action_ops { struct list_head head; char kind[IFNAMSIZ]; __u32 type; /* TBD to match kind */ + __u32 pernet_pending; size_t size; struct module *owner; int (*act)(struct sk_buff *, const struct tc_action *, diff --git a/net/sched/act_api.c b/net/sched/act_api.c index d09d068..3b51808 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -331,6 +331,7 @@ EXPORT_SYMBOL(tcf_hashinfo_destroy); static LIST_HEAD(act_base); static DEFINE_RWLOCK(act_mod_lock); +static DECLARE_WAIT_QUEUE_HEAD(act_mod_wait); int tcf_register_action(struct tc_action_ops *act, struct pernet_operations *ops) @@ -349,15 +350,18 @@ int tcf_register_action(struct tc_action_ops *act, } } list_add_tail(&act->head, &act_base); + act->pernet_pending = 1; write_unlock(&act_mod_lock); ret = register_pernet_subsys(ops); - if (ret) { - tcf_unregister_action(act, ops); - return ret; - } + write_lock(&act_mod_lock); + if (ret) + list_del(&act->head); + act->pernet_pending = 0; + write_unlock(&act_mod_lock); + wake_up_all(&act_mod_wait); - return 0; + return ret; } EXPORT_SYMBOL(tcf_register_action); @@ -367,8 +371,6 @@ int tcf_unregister_action(struct tc_action_ops *act, struct tc_action_ops *a; int err = -ENOENT; - unregister_pernet_subsys(ops); - write_lock(&act_mod_lock); list_for_each_entry(a, &act_base, head) { if (a == act) { @@ -378,10 +380,49 @@ int tcf_unregister_action(struct tc_action_ops *act, } } write_unlock(&act_mod_lock); + + if (!err) + unregister_pernet_subsys(ops); + return err; } EXPORT_SYMBOL(tcf_unregister_action); +static int tcf_module_needed(char *kind) +{ + struct tc_action_ops *a; + int rc = 0; + DEFINE_WAIT_FUNC(wait, woken_wake_function); + + if (!kind) + return rc; + + add_wait_queue(&act_mod_wait, &wait); + read_lock(&act_mod_lock); + for (;;) { + list_for_each_entry(a, &act_base, head) { + if (strcmp(kind, a->kind) == 0) + break; + } + + if (!a || strcmp(kind, a->kind) != 0) { + rc = 1; + break; + } + + if (a->pernet_pending == 0 || signal_pending(current)) + break; + + read_unlock(&act_mod_lock); + wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); + read_lock(&act_mod_lock); + } + read_unlock(&act_mod_lock); + remove_wait_queue(&act_mod_wait, &wait); + + return rc; +} + /* lookup by name */ static struct tc_action_ops *tc_lookup_action_n(char *kind) { @@ -391,11 +432,14 @@ static struct tc_action_ops *tc_lookup_action_n(char *kind) read_lock(&act_mod_lock); list_for_each_entry(a, &act_base, head) { if (strcmp(kind, a->kind) == 0) { + if (a->pernet_pending != 0) + goto out; if (try_module_get(a->owner)) res = a; break; } } +out: read_unlock(&act_mod_lock); } return res; @@ -410,11 +454,14 @@ static struct tc_action_ops *tc_lookup_action(struct nlattr *kind) read_lock(&act_mod_lock); list_for_each_entry(a, &act_base, head) { if (nla_strcmp(kind, a->kind) == 0) { + if (a->pernet_pending != 0) + goto out; if (try_module_get(a->owner)) res = a; break; } } +out: read_unlock(&act_mod_lock); } return res; @@ -549,7 +596,8 @@ struct tc_action *tcf_action_init_1(struct net *net, struct nlattr *nla, if (a_o == NULL) { #ifdef CONFIG_MODULES rtnl_unlock(); - request_module("act_%s", act_name); + if (tcf_module_needed(act_name)) + request_module("act_%s", act_name); rtnl_lock(); a_o = tc_lookup_action_n(act_name); -- 2.7.4