Commit bd3769bf authored by Linus Torvalds's avatar Linus Torvalds Committed by David S. Miller

netfilter: Fix slab corruption.

Use the correct pattern for singly linked list insertion and
deletion.  We can also calculate the list head outside of the
mutex.

Fixes: e3b37f11 ("netfilter: replace list_head with single linked list")
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
Reviewed-by: default avatarAaron Conole <aconole@bytheb.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>

net/netfilter/core.c | 108 ++++++++++++++++-----------------------------------
 1 file changed, 33 insertions(+), 75 deletions(-)
parent d24cd733
...@@ -65,49 +65,24 @@ static DEFINE_MUTEX(nf_hook_mutex); ...@@ -65,49 +65,24 @@ static DEFINE_MUTEX(nf_hook_mutex);
#define nf_entry_dereference(e) \ #define nf_entry_dereference(e) \
rcu_dereference_protected(e, lockdep_is_held(&nf_hook_mutex)) rcu_dereference_protected(e, lockdep_is_held(&nf_hook_mutex))
static struct nf_hook_entry *nf_hook_entry_head(struct net *net, static struct nf_hook_entry __rcu **nf_hook_entry_head(struct net *net, const struct nf_hook_ops *reg)
const struct nf_hook_ops *reg)
{ {
struct nf_hook_entry *hook_head = NULL;
if (reg->pf != NFPROTO_NETDEV) if (reg->pf != NFPROTO_NETDEV)
hook_head = nf_entry_dereference(net->nf.hooks[reg->pf] return net->nf.hooks[reg->pf]+reg->hooknum;
[reg->hooknum]);
else if (reg->hooknum == NF_NETDEV_INGRESS) {
#ifdef CONFIG_NETFILTER_INGRESS #ifdef CONFIG_NETFILTER_INGRESS
if (reg->hooknum == NF_NETDEV_INGRESS) {
if (reg->dev && dev_net(reg->dev) == net) if (reg->dev && dev_net(reg->dev) == net)
hook_head = return &reg->dev->nf_hooks_ingress;
nf_entry_dereference(
reg->dev->nf_hooks_ingress);
#endif
} }
return hook_head;
}
/* must hold nf_hook_mutex */
static void nf_set_hooks_head(struct net *net, const struct nf_hook_ops *reg,
struct nf_hook_entry *entry)
{
switch (reg->pf) {
case NFPROTO_NETDEV:
#ifdef CONFIG_NETFILTER_INGRESS
/* We already checked in nf_register_net_hook() that this is
* used from ingress.
*/
rcu_assign_pointer(reg->dev->nf_hooks_ingress, entry);
#endif #endif
break; return NULL;
default:
rcu_assign_pointer(net->nf.hooks[reg->pf][reg->hooknum],
entry);
break;
}
} }
int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg) int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
{ {
struct nf_hook_entry *hooks_entry; struct nf_hook_entry __rcu **pp;
struct nf_hook_entry *entry; struct nf_hook_entry *entry, *p;
if (reg->pf == NFPROTO_NETDEV) { if (reg->pf == NFPROTO_NETDEV) {
#ifndef CONFIG_NETFILTER_INGRESS #ifndef CONFIG_NETFILTER_INGRESS
...@@ -119,6 +94,10 @@ int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg) ...@@ -119,6 +94,10 @@ int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
return -EINVAL; return -EINVAL;
} }
pp = nf_hook_entry_head(net, reg);
if (!pp)
return -EINVAL;
entry = kmalloc(sizeof(*entry), GFP_KERNEL); entry = kmalloc(sizeof(*entry), GFP_KERNEL);
if (!entry) if (!entry)
return -ENOMEM; return -ENOMEM;
...@@ -128,26 +107,15 @@ int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg) ...@@ -128,26 +107,15 @@ int nf_register_net_hook(struct net *net, const struct nf_hook_ops *reg)
entry->next = NULL; entry->next = NULL;
mutex_lock(&nf_hook_mutex); mutex_lock(&nf_hook_mutex);
hooks_entry = nf_hook_entry_head(net, reg);
if (hooks_entry && hooks_entry->orig_ops->priority > reg->priority) {
/* This is the case where we need to insert at the head */
entry->next = hooks_entry;
hooks_entry = NULL;
}
while (hooks_entry &&
reg->priority >= hooks_entry->orig_ops->priority &&
nf_entry_dereference(hooks_entry->next)) {
hooks_entry = nf_entry_dereference(hooks_entry->next);
}
if (hooks_entry) { /* Find the spot in the list */
entry->next = nf_entry_dereference(hooks_entry->next); while ((p = nf_entry_dereference(*pp)) != NULL) {
rcu_assign_pointer(hooks_entry->next, entry); if (reg->priority < p->orig_ops->priority)
} else { break;
nf_set_hooks_head(net, reg, entry); pp = &p->next;
} }
rcu_assign_pointer(entry->next, p);
rcu_assign_pointer(*pp, entry);
mutex_unlock(&nf_hook_mutex); mutex_unlock(&nf_hook_mutex);
#ifdef CONFIG_NETFILTER_INGRESS #ifdef CONFIG_NETFILTER_INGRESS
...@@ -163,33 +131,23 @@ EXPORT_SYMBOL(nf_register_net_hook); ...@@ -163,33 +131,23 @@ EXPORT_SYMBOL(nf_register_net_hook);
void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg) void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg)
{ {
struct nf_hook_entry *hooks_entry; struct nf_hook_entry __rcu **pp;
struct nf_hook_entry *p;
mutex_lock(&nf_hook_mutex); pp = nf_hook_entry_head(net, reg);
hooks_entry = nf_hook_entry_head(net, reg); if (WARN_ON_ONCE(!pp))
if (hooks_entry && hooks_entry->orig_ops == reg) { return;
nf_set_hooks_head(net, reg,
nf_entry_dereference(hooks_entry->next));
goto unlock;
}
while (hooks_entry && nf_entry_dereference(hooks_entry->next)) {
struct nf_hook_entry *next =
nf_entry_dereference(hooks_entry->next);
struct nf_hook_entry *nnext;
if (next->orig_ops != reg) { mutex_lock(&nf_hook_mutex);
hooks_entry = next; while ((p = nf_entry_dereference(*pp)) != NULL) {
continue; if (p->orig_ops == reg) {
rcu_assign_pointer(*pp, p->next);
break;
} }
nnext = nf_entry_dereference(next->next); pp = &p->next;
rcu_assign_pointer(hooks_entry->next, nnext);
hooks_entry = next;
break;
} }
unlock:
mutex_unlock(&nf_hook_mutex); mutex_unlock(&nf_hook_mutex);
if (!hooks_entry) { if (!p) {
WARN(1, "nf_unregister_net_hook: hook not found!\n"); WARN(1, "nf_unregister_net_hook: hook not found!\n");
return; return;
} }
...@@ -201,10 +159,10 @@ void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg) ...@@ -201,10 +159,10 @@ void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *reg)
static_key_slow_dec(&nf_hooks_needed[reg->pf][reg->hooknum]); static_key_slow_dec(&nf_hooks_needed[reg->pf][reg->hooknum]);
#endif #endif
synchronize_net(); synchronize_net();
nf_queue_nf_hook_drop(net, hooks_entry); nf_queue_nf_hook_drop(net, p);
/* other cpu might still process nfqueue verdict that used reg */ /* other cpu might still process nfqueue verdict that used reg */
synchronize_net(); synchronize_net();
kfree(hooks_entry); kfree(p);
} }
EXPORT_SYMBOL(nf_unregister_net_hook); EXPORT_SYMBOL(nf_unregister_net_hook);
......
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