tipc: involve reference counter for node structure

TIPC node hash node table is protected with rcu lock on read side.
tipc_node_find() is used to look for a node object with node address
through iterating the hash node table. As the entire process of what
tipc_node_find() traverses the table is guarded with rcu read lock,
it's safe for us. However, when callers use the node object returned
by tipc_node_find(), there is no rcu read lock applied. Therefore,
this is absolutely unsafe for callers of tipc_node_find().

Now we introduce a reference counter for node structure. Before
tipc_node_find() returns node object to its caller, it first increases
the reference counter. Accordingly, after its caller used it up,
it decreases the counter again. This can prevent a node being used by
one thread from being freed by another thread.

Reviewed-by: Erik Hugne <erik.hugne@ericsson.com>
Reviewed-by: Jon Maloy <jon.maloy@ericson.com>
Signed-off-by: Ying Xue <ying.xue@windriver.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Ying Xue 2015-03-26 18:10:24 +08:00 committed by David S. Miller
parent b952b2befb
commit 8a0f6ebe84
6 changed files with 79 additions and 30 deletions

View File

@ -329,13 +329,12 @@ static void bclink_peek_nack(struct net *net, struct tipc_msg *msg)
return; return;
tipc_node_lock(n_ptr); tipc_node_lock(n_ptr);
if (n_ptr->bclink.recv_permitted && if (n_ptr->bclink.recv_permitted &&
(n_ptr->bclink.last_in != n_ptr->bclink.last_sent) && (n_ptr->bclink.last_in != n_ptr->bclink.last_sent) &&
(n_ptr->bclink.last_in == msg_bcgap_after(msg))) (n_ptr->bclink.last_in == msg_bcgap_after(msg)))
n_ptr->bclink.oos_state = 2; n_ptr->bclink.oos_state = 2;
tipc_node_unlock(n_ptr); tipc_node_unlock(n_ptr);
tipc_node_put(n_ptr);
} }
/* tipc_bclink_xmit - deliver buffer chain to all nodes in cluster /* tipc_bclink_xmit - deliver buffer chain to all nodes in cluster
@ -466,6 +465,7 @@ void tipc_bclink_rcv(struct net *net, struct sk_buff *buf)
tipc_node_unlock(node); tipc_node_unlock(node);
bclink_peek_nack(net, msg); bclink_peek_nack(net, msg);
} }
tipc_node_put(node);
goto exit; goto exit;
} }
@ -570,6 +570,7 @@ receive:
unlock: unlock:
tipc_node_unlock(node); tipc_node_unlock(node);
tipc_node_put(node);
exit: exit:
kfree_skb(buf); kfree_skb(buf);
} }

View File

@ -260,6 +260,7 @@ void tipc_disc_rcv(struct net *net, struct sk_buff *buf,
} }
} }
tipc_node_unlock(node); tipc_node_unlock(node);
tipc_node_put(node);
} }
/** /**

View File

@ -854,6 +854,7 @@ int tipc_link_xmit(struct net *net, struct sk_buff_head *list, u32 dnode,
if (link) if (link)
rc = __tipc_link_xmit(net, link, list); rc = __tipc_link_xmit(net, link, list);
tipc_node_unlock(node); tipc_node_unlock(node);
tipc_node_put(node);
} }
if (link) if (link)
return rc; return rc;
@ -1116,8 +1117,8 @@ void tipc_rcv(struct net *net, struct sk_buff *skb, struct tipc_bearer *b_ptr)
n_ptr = tipc_node_find(net, msg_prevnode(msg)); n_ptr = tipc_node_find(net, msg_prevnode(msg));
if (unlikely(!n_ptr)) if (unlikely(!n_ptr))
goto discard; goto discard;
tipc_node_lock(n_ptr);
tipc_node_lock(n_ptr);
/* Locate unicast link endpoint that should handle message */ /* Locate unicast link endpoint that should handle message */
l_ptr = n_ptr->links[b_ptr->identity]; l_ptr = n_ptr->links[b_ptr->identity];
if (unlikely(!l_ptr)) if (unlikely(!l_ptr))
@ -1205,6 +1206,7 @@ void tipc_rcv(struct net *net, struct sk_buff *skb, struct tipc_bearer *b_ptr)
skb = NULL; skb = NULL;
unlock: unlock:
tipc_node_unlock(n_ptr); tipc_node_unlock(n_ptr);
tipc_node_put(n_ptr);
discard: discard:
if (unlikely(skb)) if (unlikely(skb))
kfree_skb(skb); kfree_skb(skb);
@ -2236,7 +2238,6 @@ int tipc_nl_link_dump(struct sk_buff *skb, struct netlink_callback *cb)
msg.seq = cb->nlh->nlmsg_seq; msg.seq = cb->nlh->nlmsg_seq;
rcu_read_lock(); rcu_read_lock();
if (prev_node) { if (prev_node) {
node = tipc_node_find(net, prev_node); node = tipc_node_find(net, prev_node);
if (!node) { if (!node) {
@ -2249,6 +2250,7 @@ int tipc_nl_link_dump(struct sk_buff *skb, struct netlink_callback *cb)
cb->prev_seq = 1; cb->prev_seq = 1;
goto out; goto out;
} }
tipc_node_put(node);
list_for_each_entry_continue_rcu(node, &tn->node_list, list_for_each_entry_continue_rcu(node, &tn->node_list,
list) { list) {
@ -2256,6 +2258,7 @@ int tipc_nl_link_dump(struct sk_buff *skb, struct netlink_callback *cb)
err = __tipc_nl_add_node_links(net, &msg, node, err = __tipc_nl_add_node_links(net, &msg, node,
&prev_link); &prev_link);
tipc_node_unlock(node); tipc_node_unlock(node);
tipc_node_put(node);
if (err) if (err)
goto out; goto out;

View File

@ -244,6 +244,7 @@ static void tipc_publ_subscribe(struct net *net, struct publication *publ,
tipc_node_lock(node); tipc_node_lock(node);
list_add_tail(&publ->nodesub_list, &node->publ_list); list_add_tail(&publ->nodesub_list, &node->publ_list);
tipc_node_unlock(node); tipc_node_unlock(node);
tipc_node_put(node);
} }
static void tipc_publ_unsubscribe(struct net *net, struct publication *publ, static void tipc_publ_unsubscribe(struct net *net, struct publication *publ,
@ -258,6 +259,7 @@ static void tipc_publ_unsubscribe(struct net *net, struct publication *publ,
tipc_node_lock(node); tipc_node_lock(node);
list_del_init(&publ->nodesub_list); list_del_init(&publ->nodesub_list);
tipc_node_unlock(node); tipc_node_unlock(node);
tipc_node_put(node);
} }
/** /**

View File

@ -42,6 +42,7 @@
static void node_lost_contact(struct tipc_node *n_ptr); static void node_lost_contact(struct tipc_node *n_ptr);
static void node_established_contact(struct tipc_node *n_ptr); static void node_established_contact(struct tipc_node *n_ptr);
static void tipc_node_delete(struct tipc_node *node);
struct tipc_sock_conn { struct tipc_sock_conn {
u32 port; u32 port;
@ -67,6 +68,23 @@ static unsigned int tipc_hashfn(u32 addr)
return addr & (NODE_HTABLE_SIZE - 1); return addr & (NODE_HTABLE_SIZE - 1);
} }
static void tipc_node_kref_release(struct kref *kref)
{
struct tipc_node *node = container_of(kref, struct tipc_node, kref);
tipc_node_delete(node);
}
void tipc_node_put(struct tipc_node *node)
{
kref_put(&node->kref, tipc_node_kref_release);
}
static void tipc_node_get(struct tipc_node *node)
{
kref_get(&node->kref);
}
/* /*
* tipc_node_find - locate specified node object, if it exists * tipc_node_find - locate specified node object, if it exists
*/ */
@ -82,6 +100,7 @@ struct tipc_node *tipc_node_find(struct net *net, u32 addr)
hlist_for_each_entry_rcu(node, &tn->node_htable[tipc_hashfn(addr)], hlist_for_each_entry_rcu(node, &tn->node_htable[tipc_hashfn(addr)],
hash) { hash) {
if (node->addr == addr) { if (node->addr == addr) {
tipc_node_get(node);
rcu_read_unlock(); rcu_read_unlock();
return node; return node;
} }
@ -106,6 +125,7 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr)
} }
n_ptr->addr = addr; n_ptr->addr = addr;
n_ptr->net = net; n_ptr->net = net;
kref_init(&n_ptr->kref);
spin_lock_init(&n_ptr->lock); spin_lock_init(&n_ptr->lock);
INIT_HLIST_NODE(&n_ptr->hash); INIT_HLIST_NODE(&n_ptr->hash);
INIT_LIST_HEAD(&n_ptr->list); INIT_LIST_HEAD(&n_ptr->list);
@ -120,16 +140,17 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr)
list_add_tail_rcu(&n_ptr->list, &temp_node->list); list_add_tail_rcu(&n_ptr->list, &temp_node->list);
n_ptr->action_flags = TIPC_WAIT_PEER_LINKS_DOWN; n_ptr->action_flags = TIPC_WAIT_PEER_LINKS_DOWN;
n_ptr->signature = INVALID_NODE_SIG; n_ptr->signature = INVALID_NODE_SIG;
tipc_node_get(n_ptr);
exit: exit:
spin_unlock_bh(&tn->node_list_lock); spin_unlock_bh(&tn->node_list_lock);
return n_ptr; return n_ptr;
} }
static void tipc_node_delete(struct tipc_net *tn, struct tipc_node *n_ptr) static void tipc_node_delete(struct tipc_node *node)
{ {
list_del_rcu(&n_ptr->list); list_del_rcu(&node->list);
hlist_del_rcu(&n_ptr->hash); hlist_del_rcu(&node->hash);
kfree_rcu(n_ptr, rcu); kfree_rcu(node, rcu);
} }
void tipc_node_stop(struct net *net) void tipc_node_stop(struct net *net)
@ -139,7 +160,7 @@ void tipc_node_stop(struct net *net)
spin_lock_bh(&tn->node_list_lock); spin_lock_bh(&tn->node_list_lock);
list_for_each_entry_safe(node, t_node, &tn->node_list, list) list_for_each_entry_safe(node, t_node, &tn->node_list, list)
tipc_node_delete(tn, node); tipc_node_put(node);
spin_unlock_bh(&tn->node_list_lock); spin_unlock_bh(&tn->node_list_lock);
} }
@ -147,6 +168,7 @@ int tipc_node_add_conn(struct net *net, u32 dnode, u32 port, u32 peer_port)
{ {
struct tipc_node *node; struct tipc_node *node;
struct tipc_sock_conn *conn; struct tipc_sock_conn *conn;
int err = 0;
if (in_own_node(net, dnode)) if (in_own_node(net, dnode))
return 0; return 0;
@ -157,8 +179,10 @@ int tipc_node_add_conn(struct net *net, u32 dnode, u32 port, u32 peer_port)
return -EHOSTUNREACH; return -EHOSTUNREACH;
} }
conn = kmalloc(sizeof(*conn), GFP_ATOMIC); conn = kmalloc(sizeof(*conn), GFP_ATOMIC);
if (!conn) if (!conn) {
return -EHOSTUNREACH; err = -EHOSTUNREACH;
goto exit;
}
conn->peer_node = dnode; conn->peer_node = dnode;
conn->port = port; conn->port = port;
conn->peer_port = peer_port; conn->peer_port = peer_port;
@ -166,7 +190,9 @@ int tipc_node_add_conn(struct net *net, u32 dnode, u32 port, u32 peer_port)
tipc_node_lock(node); tipc_node_lock(node);
list_add_tail(&conn->list, &node->conn_sks); list_add_tail(&conn->list, &node->conn_sks);
tipc_node_unlock(node); tipc_node_unlock(node);
return 0; exit:
tipc_node_put(node);
return err;
} }
void tipc_node_remove_conn(struct net *net, u32 dnode, u32 port) void tipc_node_remove_conn(struct net *net, u32 dnode, u32 port)
@ -189,6 +215,7 @@ void tipc_node_remove_conn(struct net *net, u32 dnode, u32 port)
kfree(conn); kfree(conn);
} }
tipc_node_unlock(node); tipc_node_unlock(node);
tipc_node_put(node);
} }
/** /**
@ -417,19 +444,25 @@ int tipc_node_get_linkname(struct net *net, u32 bearer_id, u32 addr,
char *linkname, size_t len) char *linkname, size_t len)
{ {
struct tipc_link *link; struct tipc_link *link;
int err = -EINVAL;
struct tipc_node *node = tipc_node_find(net, addr); struct tipc_node *node = tipc_node_find(net, addr);
if ((bearer_id >= MAX_BEARERS) || !node) if (!node)
return -EINVAL; return err;
if (bearer_id >= MAX_BEARERS)
goto exit;
tipc_node_lock(node); tipc_node_lock(node);
link = node->links[bearer_id]; link = node->links[bearer_id];
if (link) { if (link) {
strncpy(linkname, link->name, len); strncpy(linkname, link->name, len);
tipc_node_unlock(node); err = 0;
return 0;
} }
exit:
tipc_node_unlock(node); tipc_node_unlock(node);
return -EINVAL; tipc_node_put(node);
return err;
} }
void tipc_node_unlock(struct tipc_node *node) void tipc_node_unlock(struct tipc_node *node)
@ -545,17 +578,21 @@ int tipc_nl_node_dump(struct sk_buff *skb, struct netlink_callback *cb)
msg.seq = cb->nlh->nlmsg_seq; msg.seq = cb->nlh->nlmsg_seq;
rcu_read_lock(); rcu_read_lock();
if (last_addr) {
if (last_addr && !tipc_node_find(net, last_addr)) { node = tipc_node_find(net, last_addr);
rcu_read_unlock(); if (!node) {
/* We never set seq or call nl_dump_check_consistent() this rcu_read_unlock();
* means that setting prev_seq here will cause the consistence /* We never set seq or call nl_dump_check_consistent()
* check to fail in the netlink callback handler. Resulting in * this means that setting prev_seq here will cause the
* the NLMSG_DONE message having the NLM_F_DUMP_INTR flag set if * consistence check to fail in the netlink callback
* the node state changed while we released the lock. * handler. Resulting in the NLMSG_DONE message having
*/ * the NLM_F_DUMP_INTR flag set if the node state
cb->prev_seq = 1; * changed while we released the lock.
return -EPIPE; */
cb->prev_seq = 1;
return -EPIPE;
}
tipc_node_put(node);
} }
list_for_each_entry_rcu(node, &tn->node_list, list) { list_for_each_entry_rcu(node, &tn->node_list, list) {

View File

@ -94,6 +94,7 @@ struct tipc_node_bclink {
/** /**
* struct tipc_node - TIPC node structure * struct tipc_node - TIPC node structure
* @addr: network address of node * @addr: network address of node
* @ref: reference counter to node object
* @lock: spinlock governing access to structure * @lock: spinlock governing access to structure
* @net: the applicable net namespace * @net: the applicable net namespace
* @hash: links to adjacent nodes in unsorted hash chain * @hash: links to adjacent nodes in unsorted hash chain
@ -115,6 +116,7 @@ struct tipc_node_bclink {
*/ */
struct tipc_node { struct tipc_node {
u32 addr; u32 addr;
struct kref kref;
spinlock_t lock; spinlock_t lock;
struct net *net; struct net *net;
struct hlist_node hash; struct hlist_node hash;
@ -137,6 +139,7 @@ struct tipc_node {
}; };
struct tipc_node *tipc_node_find(struct net *net, u32 addr); struct tipc_node *tipc_node_find(struct net *net, u32 addr);
void tipc_node_put(struct tipc_node *node);
struct tipc_node *tipc_node_create(struct net *net, u32 addr); struct tipc_node *tipc_node_create(struct net *net, u32 addr);
void tipc_node_stop(struct net *net); void tipc_node_stop(struct net *net);
void tipc_node_attach_link(struct tipc_node *n_ptr, struct tipc_link *l_ptr); void tipc_node_attach_link(struct tipc_node *n_ptr, struct tipc_link *l_ptr);
@ -171,10 +174,12 @@ static inline uint tipc_node_get_mtu(struct net *net, u32 addr, u32 selector)
node = tipc_node_find(net, addr); node = tipc_node_find(net, addr);
if (likely(node)) if (likely(node)) {
mtu = node->act_mtus[selector & 1]; mtu = node->act_mtus[selector & 1];
else tipc_node_put(node);
} else {
mtu = MAX_MSG_SIZE; mtu = MAX_MSG_SIZE;
}
return mtu; return mtu;
} }