Commit b75911b66ad508a3c3f006ce37d9f9ebee34da43

Authored by Liping Zhang
Committed by Pablo Neira Ayuso
1 parent 12be15dd5a

netfilter: cttimeout: fix use after free error when delete netns

In general, when we want to delete a netns, cttimeout_net_exit will
be called before ipt_unregister_table, i.e. before ctnl_timeout_put.

But after call kfree_rcu in cttimeout_net_exit, we will still decrease
the timeout object's refcnt in ctnl_timeout_put, this is incorrect,
and will cause a use after free error.

It is easy to reproduce this problem:
  # while : ; do
  ip netns add xxx
  ip netns exec xxx nfct add timeout testx inet icmp timeout 200
  ip netns exec xxx iptables -t raw -p icmp -I OUTPUT -j CT --timeout testx
  ip netns del xxx
  done

  =======================================================================
  BUG kmalloc-96 (Tainted: G    B       E  ): Poison overwritten
  -----------------------------------------------------------------------
  INFO: 0xffff88002b5161e8-0xffff88002b5161e8. First byte 0x6a instead of
  0x6b
  INFO: Allocated in cttimeout_new_timeout+0xd4/0x240 [nfnetlink_cttimeout]
  age=104 cpu=0 pid=3330
  ___slab_alloc+0x4da/0x540
  __slab_alloc+0x20/0x40
  __kmalloc+0x1c8/0x240
  cttimeout_new_timeout+0xd4/0x240 [nfnetlink_cttimeout]
  nfnetlink_rcv_msg+0x21a/0x230 [nfnetlink]
  [ ... ]

So only when the refcnt decreased to 0, we call kfree_rcu to free the
timeout object. And like nfnetlink_acct do, use atomic_cmpxchg to
avoid race between ctnl_timeout_try_del and ctnl_timeout_put.

Signed-off-by: Liping Zhang <liping.zhang@spreadtrum.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

Showing 1 changed file with 10 additions and 6 deletions Side-by-side Diff

net/netfilter/nfnetlink_cttimeout.c
... ... @@ -330,16 +330,16 @@
330 330 {
331 331 int ret = 0;
332 332  
333   - /* we want to avoid races with nf_ct_timeout_find_get. */
334   - if (atomic_dec_and_test(&timeout->refcnt)) {
  333 + /* We want to avoid races with ctnl_timeout_put. So only when the
  334 + * current refcnt is 1, we decrease it to 0.
  335 + */
  336 + if (atomic_cmpxchg(&timeout->refcnt, 1, 0) == 1) {
335 337 /* We are protected by nfnl mutex. */
336 338 list_del_rcu(&timeout->head);
337 339 nf_ct_l4proto_put(timeout->l4proto);
338 340 ctnl_untimeout(net, timeout);
339 341 kfree_rcu(timeout, rcu_head);
340 342 } else {
341   - /* still in use, restore reference counter. */
342   - atomic_inc(&timeout->refcnt);
343 343 ret = -EBUSY;
344 344 }
345 345 return ret;
... ... @@ -543,7 +543,9 @@
543 543  
544 544 static void ctnl_timeout_put(struct ctnl_timeout *timeout)
545 545 {
546   - atomic_dec(&timeout->refcnt);
  546 + if (atomic_dec_and_test(&timeout->refcnt))
  547 + kfree_rcu(timeout, rcu_head);
  548 +
547 549 module_put(THIS_MODULE);
548 550 }
549 551 #endif /* CONFIG_NF_CONNTRACK_TIMEOUT */
... ... @@ -591,7 +593,9 @@
591 593 list_for_each_entry_safe(cur, tmp, &net->nfct_timeout_list, head) {
592 594 list_del_rcu(&cur->head);
593 595 nf_ct_l4proto_put(cur->l4proto);
594   - kfree_rcu(cur, rcu_head);
  596 +
  597 + if (atomic_dec_and_test(&cur->refcnt))
  598 + kfree_rcu(cur, rcu_head);
595 599 }
596 600 }
597 601