peernet2id_alloc() is racy without rtnl_lock() as atomic_read(&peer->count)
under net->nsid_lock does not guarantee, peer is alive:
rcu_read_lock()
peernet2id_alloc() ..
spin_lock_bh(&net->nsid_lock) ..
atomic_read(&peer->count) == 1 ..
.. put_net()
.. cleanup_net()
.. for_each_net(tmp)
.. spin_lock_bh(&tmp->nsid_lock)
.. __peernet2id(tmp, net) == -1
.. ..
.. ..
__peernet2id_alloc(alloc == true) ..
.. ..
rcu_read_unlock() ..
.. synchronize_rcu()
.. kmem_cache_free(net)
After the above situation, net::netns_id contains id pointing to freed memory,
and any other dereferencing by the id will operate with this freed memory.
Currently, peernet2id_alloc() is used under rtnl_lock() everywhere except
ovs_vport_cmd_fill_info(), and this race can't occur. But peernet2id_alloc()
is generic interface, and better we fix it before someone really starts
use it in wrong context.
Signed-off-by: Kirill Tkhai <redacted>
---
net/core/net_namespace.c | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
@@ -221,17 +221,32 @@ static void rtnl_net_notifyid(struct net *net, int cmd, int id);*/intpeernet2id_alloc(structnet*net,structnet*peer){-boolalloc;+boolalloc=false,alive=false;intid;-if(atomic_read(&net->count)==0)-returnNETNSA_NSID_NOT_ASSIGNED;spin_lock_bh(&net->nsid_lock);-alloc=atomic_read(&peer->count)==0?false:true;+/* Spinlock guarantees we never hash a peer to net->netns_ids+*afteridr_destroy(&net->netns_ids)occursincleanup_net().+*/+if(atomic_read(&net->count)==0){+id=NETNSA_NSID_NOT_ASSIGNED;+gotounlock;+}+/*+*WhenpeerisobtainedfromRCUlists,wemayracewith+*itscleanup.Checkwhetherit'salive,andthisguarantees+*weneverhashapeerbacktonet->netns_ids,afterithas+*justbeenidr_remove()'dfromthereincleanup_net().+*/+if(maybe_get_net(peer))+alive=alloc=true;id=__peernet2id_alloc(net,peer,&alloc);+unlock:spin_unlock_bh(&net->nsid_lock);if(alloc&&id>=0)rtnl_net_notifyid(net,RTM_NEWNSID,id);+if(alive)+put_net(peer);returnid;}EXPORT_SYMBOL_GPL(peernet2id_alloc);
Since people may mistakenly obtain destroying net
from net_namespace_list and from net::netns_ids
without checking for its net::counter, let's protect
against such situations and insert BUG_ON() to stop
move on after this.
Panic is better, than memory corruption and undefined
behavior.
Signed-off-by: Kirill Tkhai <redacted>
---
include/net/net_namespace.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
idr_find() is safe under rcu_read_lock() and
maybe_get_net() guarantees that net is alive.
Signed-off-by: Kirill Tkhai <redacted>
---
net/core/net_namespace.c | 2 --
1 file changed, 2 deletions(-)
@@ -279,11 +279,9 @@ struct net *get_net_ns_by_id(struct net *net, int id)returnNULL;rcu_read_lock();-spin_lock_bh(&net->nsid_lock);peer=idr_find(&net->netns_ids,id);if(peer)peer=maybe_get_net(peer);-spin_unlock_bh(&net->nsid_lock);rcu_read_unlock();returnpeer;
From: Eric W. Biederman <hidden> Date: 2017-12-29 18:19:10
Kirill Tkhai [off-list ref] writes:
peernet2id_alloc() is racy without rtnl_lock() as atomic_read(&peer->count)
under net->nsid_lock does not guarantee, peer is alive:
rcu_read_lock()
peernet2id_alloc() ..
spin_lock_bh(&net->nsid_lock) ..
atomic_read(&peer->count) == 1 ..
.. put_net()
.. cleanup_net()
.. for_each_net(tmp)
.. spin_lock_bh(&tmp->nsid_lock)
.. __peernet2id(tmp, net) == -1
.. ..
.. ..
__peernet2id_alloc(alloc == true) ..
.. ..
rcu_read_unlock() ..
.. synchronize_rcu()
.. kmem_cache_free(net)
After the above situation, net::netns_id contains id pointing to freed memory,
and any other dereferencing by the id will operate with this freed memory.
Currently, peernet2id_alloc() is used under rtnl_lock() everywhere except
ovs_vport_cmd_fill_info(), and this race can't occur. But peernet2id_alloc()
is generic interface, and better we fix it before someone really starts
use it in wrong context.
Nacked-by: "Eric W. Biederman" [off-list ref]
I have already made a clear objection to the first unnecessary and
confusing hunk. Simply resending the muddle headed code doesn't make it
better.
Eric
@@ -221,17 +221,32 @@ static void rtnl_net_notifyid(struct net *net, int cmd, int id);*/intpeernet2id_alloc(structnet*net,structnet*peer){-boolalloc;+boolalloc=false,alive=false;intid;-if(atomic_read(&net->count)==0)-returnNETNSA_NSID_NOT_ASSIGNED;spin_lock_bh(&net->nsid_lock);-alloc=atomic_read(&peer->count)==0?false:true;+/* Spinlock guarantees we never hash a peer to net->netns_ids+*afteridr_destroy(&net->netns_ids)occursincleanup_net().+*/+if(atomic_read(&net->count)==0){+id=NETNSA_NSID_NOT_ASSIGNED;+gotounlock;+}+/*+*WhenpeerisobtainedfromRCUlists,wemayracewith+*itscleanup.Checkwhetherit'salive,andthisguarantees+*weneverhashapeerbacktonet->netns_ids,afterithas+*justbeenidr_remove()'dfromthereincleanup_net().+*/+if(maybe_get_net(peer))+alive=alloc=true;id=__peernet2id_alloc(net,peer,&alloc);+unlock:spin_unlock_bh(&net->nsid_lock);if(alloc&&id>=0)rtnl_net_notifyid(net,RTM_NEWNSID,id);+if(alive)+put_net(peer);returnid;}EXPORT_SYMBOL_GPL(peernet2id_alloc);
peernet2id_alloc() is racy without rtnl_lock() as atomic_read(&peer->count)
under net->nsid_lock does not guarantee, peer is alive:
rcu_read_lock()
peernet2id_alloc() ..
spin_lock_bh(&net->nsid_lock) ..
atomic_read(&peer->count) == 1 ..
.. put_net()
.. cleanup_net()
.. for_each_net(tmp)
.. spin_lock_bh(&tmp->nsid_lock)
.. __peernet2id(tmp, net) == -1
.. ..
.. ..
__peernet2id_alloc(alloc == true) ..
.. ..
rcu_read_unlock() ..
.. synchronize_rcu()
.. kmem_cache_free(net)
After the above situation, net::netns_id contains id pointing to freed memory,
and any other dereferencing by the id will operate with this freed memory.
Currently, peernet2id_alloc() is used under rtnl_lock() everywhere except
ovs_vport_cmd_fill_info(), and this race can't occur. But peernet2id_alloc()
is generic interface, and better we fix it before someone really starts
use it in wrong context.
Nacked-by: "Eric W. Biederman" [off-list ref]
I have already made a clear objection to the first unnecessary and
confusing hunk. Simply resending the muddle headed code doesn't make it
better.
You provided comments on my changes and you asked couple of questions. I replied
your questions and explained, why it seems important to made the first hunk. Since
there were questions from you I interpreted the conversation is a discussion. Later
there was no an answer from you, and patchset status became not clear for me, and I
wrote about that. I had no an aim to disappoint you or ignore your position.
Thank you for the reply. Now the position is clear for me. I'll remove the first
hunk and resend the changed patchset like you suggested.
Kirill
@@ -221,17 +221,32 @@ static void rtnl_net_notifyid(struct net *net, int cmd, int id);*/intpeernet2id_alloc(structnet*net,structnet*peer){-boolalloc;+boolalloc=false,alive=false;intid;-if(atomic_read(&net->count)==0)-returnNETNSA_NSID_NOT_ASSIGNED;spin_lock_bh(&net->nsid_lock);-alloc=atomic_read(&peer->count)==0?false:true;+/* Spinlock guarantees we never hash a peer to net->netns_ids+*afteridr_destroy(&net->netns_ids)occursincleanup_net().+*/+if(atomic_read(&net->count)==0){+id=NETNSA_NSID_NOT_ASSIGNED;+gotounlock;+}+/*+*WhenpeerisobtainedfromRCUlists,wemayracewith+*itscleanup.Checkwhetherit'salive,andthisguarantees+*weneverhashapeerbacktonet->netns_ids,afterithas+*justbeenidr_remove()'dfromthereincleanup_net().+*/+if(maybe_get_net(peer))+alive=alloc=true;id=__peernet2id_alloc(net,peer,&alloc);+unlock:spin_unlock_bh(&net->nsid_lock);if(alloc&&id>=0)rtnl_net_notifyid(net,RTM_NEWNSID,id);+if(alive)+put_net(peer);returnid;}EXPORT_SYMBOL_GPL(peernet2id_alloc);