linux/net/batman-adv/icmp_socket.c
Marek Lindner 44524fcdf6 batman-adv: Correct rcu refcounting for neigh_node
It might be possible that 2 threads access the same data in the same
rcu grace period. The first thread calls call_rcu() to decrement the
refcount and free the data while the second thread increases the
refcount to use the data. To avoid this race condition all refcount
operations have to be atomic.

Reported-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Marek Lindner <lindner_marek@yahoo.de>
2011-03-05 12:50:03 +01:00

370 lines
8.9 KiB
C

/*
* Copyright (C) 2007-2011 B.A.T.M.A.N. contributors:
*
* Marek Lindner
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*
*/
#include "main.h"
#include <linux/debugfs.h>
#include <linux/slab.h>
#include "icmp_socket.h"
#include "send.h"
#include "hash.h"
#include "originator.h"
#include "hard-interface.h"
static struct socket_client *socket_client_hash[256];
static void bat_socket_add_packet(struct socket_client *socket_client,
struct icmp_packet_rr *icmp_packet,
size_t icmp_len);
void bat_socket_init(void)
{
memset(socket_client_hash, 0, sizeof(socket_client_hash));
}
static int bat_socket_open(struct inode *inode, struct file *file)
{
unsigned int i;
struct socket_client *socket_client;
nonseekable_open(inode, file);
socket_client = kmalloc(sizeof(struct socket_client), GFP_KERNEL);
if (!socket_client)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(socket_client_hash); i++) {
if (!socket_client_hash[i]) {
socket_client_hash[i] = socket_client;
break;
}
}
if (i == ARRAY_SIZE(socket_client_hash)) {
pr_err("Error - can't add another packet client: "
"maximum number of clients reached\n");
kfree(socket_client);
return -EXFULL;
}
INIT_LIST_HEAD(&socket_client->queue_list);
socket_client->queue_len = 0;
socket_client->index = i;
socket_client->bat_priv = inode->i_private;
spin_lock_init(&socket_client->lock);
init_waitqueue_head(&socket_client->queue_wait);
file->private_data = socket_client;
inc_module_count();
return 0;
}
static int bat_socket_release(struct inode *inode, struct file *file)
{
struct socket_client *socket_client = file->private_data;
struct socket_packet *socket_packet;
struct list_head *list_pos, *list_pos_tmp;
spin_lock_bh(&socket_client->lock);
/* for all packets in the queue ... */
list_for_each_safe(list_pos, list_pos_tmp, &socket_client->queue_list) {
socket_packet = list_entry(list_pos,
struct socket_packet, list);
list_del(list_pos);
kfree(socket_packet);
}
socket_client_hash[socket_client->index] = NULL;
spin_unlock_bh(&socket_client->lock);
kfree(socket_client);
dec_module_count();
return 0;
}
static ssize_t bat_socket_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct socket_client *socket_client = file->private_data;
struct socket_packet *socket_packet;
size_t packet_len;
int error;
if ((file->f_flags & O_NONBLOCK) && (socket_client->queue_len == 0))
return -EAGAIN;
if ((!buf) || (count < sizeof(struct icmp_packet)))
return -EINVAL;
if (!access_ok(VERIFY_WRITE, buf, count))
return -EFAULT;
error = wait_event_interruptible(socket_client->queue_wait,
socket_client->queue_len);
if (error)
return error;
spin_lock_bh(&socket_client->lock);
socket_packet = list_first_entry(&socket_client->queue_list,
struct socket_packet, list);
list_del(&socket_packet->list);
socket_client->queue_len--;
spin_unlock_bh(&socket_client->lock);
error = __copy_to_user(buf, &socket_packet->icmp_packet,
socket_packet->icmp_len);
packet_len = socket_packet->icmp_len;
kfree(socket_packet);
if (error)
return -EFAULT;
return packet_len;
}
static ssize_t bat_socket_write(struct file *file, const char __user *buff,
size_t len, loff_t *off)
{
struct socket_client *socket_client = file->private_data;
struct bat_priv *bat_priv = socket_client->bat_priv;
struct sk_buff *skb;
struct icmp_packet_rr *icmp_packet;
struct orig_node *orig_node = NULL;
struct neigh_node *neigh_node = NULL;
struct batman_if *batman_if;
size_t packet_len = sizeof(struct icmp_packet);
uint8_t dstaddr[ETH_ALEN];
if (len < sizeof(struct icmp_packet)) {
bat_dbg(DBG_BATMAN, bat_priv,
"Error - can't send packet from char device: "
"invalid packet size\n");
return -EINVAL;
}
if (!bat_priv->primary_if)
return -EFAULT;
if (len >= sizeof(struct icmp_packet_rr))
packet_len = sizeof(struct icmp_packet_rr);
skb = dev_alloc_skb(packet_len + sizeof(struct ethhdr));
if (!skb)
return -ENOMEM;
skb_reserve(skb, sizeof(struct ethhdr));
icmp_packet = (struct icmp_packet_rr *)skb_put(skb, packet_len);
if (!access_ok(VERIFY_READ, buff, packet_len)) {
len = -EFAULT;
goto free_skb;
}
if (__copy_from_user(icmp_packet, buff, packet_len)) {
len = -EFAULT;
goto free_skb;
}
if (icmp_packet->packet_type != BAT_ICMP) {
bat_dbg(DBG_BATMAN, bat_priv,
"Error - can't send packet from char device: "
"got bogus packet type (expected: BAT_ICMP)\n");
len = -EINVAL;
goto free_skb;
}
if (icmp_packet->msg_type != ECHO_REQUEST) {
bat_dbg(DBG_BATMAN, bat_priv,
"Error - can't send packet from char device: "
"got bogus message type (expected: ECHO_REQUEST)\n");
len = -EINVAL;
goto free_skb;
}
icmp_packet->uid = socket_client->index;
if (icmp_packet->version != COMPAT_VERSION) {
icmp_packet->msg_type = PARAMETER_PROBLEM;
icmp_packet->ttl = COMPAT_VERSION;
bat_socket_add_packet(socket_client, icmp_packet, packet_len);
goto free_skb;
}
if (atomic_read(&bat_priv->mesh_state) != MESH_ACTIVE)
goto dst_unreach;
spin_lock_bh(&bat_priv->orig_hash_lock);
rcu_read_lock();
orig_node = ((struct orig_node *)hash_find(bat_priv->orig_hash,
compare_orig, choose_orig,
icmp_packet->dst));
if (!orig_node)
goto unlock;
kref_get(&orig_node->refcount);
neigh_node = orig_node->router;
if (!neigh_node)
goto unlock;
if (!atomic_inc_not_zero(&neigh_node->refcount)) {
neigh_node = NULL;
goto unlock;
}
rcu_read_unlock();
batman_if = orig_node->router->if_incoming;
memcpy(dstaddr, orig_node->router->addr, ETH_ALEN);
spin_unlock_bh(&bat_priv->orig_hash_lock);
if (!batman_if)
goto dst_unreach;
if (batman_if->if_status != IF_ACTIVE)
goto dst_unreach;
memcpy(icmp_packet->orig,
bat_priv->primary_if->net_dev->dev_addr, ETH_ALEN);
if (packet_len == sizeof(struct icmp_packet_rr))
memcpy(icmp_packet->rr,
batman_if->net_dev->dev_addr, ETH_ALEN);
send_skb_packet(skb, batman_if, dstaddr);
goto out;
unlock:
rcu_read_unlock();
spin_unlock_bh(&bat_priv->orig_hash_lock);
dst_unreach:
icmp_packet->msg_type = DESTINATION_UNREACHABLE;
bat_socket_add_packet(socket_client, icmp_packet, packet_len);
free_skb:
kfree_skb(skb);
out:
if (neigh_node)
neigh_node_free_ref(neigh_node);
if (orig_node)
kref_put(&orig_node->refcount, orig_node_free_ref);
return len;
}
static unsigned int bat_socket_poll(struct file *file, poll_table *wait)
{
struct socket_client *socket_client = file->private_data;
poll_wait(file, &socket_client->queue_wait, wait);
if (socket_client->queue_len > 0)
return POLLIN | POLLRDNORM;
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.open = bat_socket_open,
.release = bat_socket_release,
.read = bat_socket_read,
.write = bat_socket_write,
.poll = bat_socket_poll,
.llseek = no_llseek,
};
int bat_socket_setup(struct bat_priv *bat_priv)
{
struct dentry *d;
if (!bat_priv->debug_dir)
goto err;
d = debugfs_create_file(ICMP_SOCKET, S_IFREG | S_IWUSR | S_IRUSR,
bat_priv->debug_dir, bat_priv, &fops);
if (d)
goto err;
return 0;
err:
return 1;
}
static void bat_socket_add_packet(struct socket_client *socket_client,
struct icmp_packet_rr *icmp_packet,
size_t icmp_len)
{
struct socket_packet *socket_packet;
socket_packet = kmalloc(sizeof(struct socket_packet), GFP_ATOMIC);
if (!socket_packet)
return;
INIT_LIST_HEAD(&socket_packet->list);
memcpy(&socket_packet->icmp_packet, icmp_packet, icmp_len);
socket_packet->icmp_len = icmp_len;
spin_lock_bh(&socket_client->lock);
/* while waiting for the lock the socket_client could have been
* deleted */
if (!socket_client_hash[icmp_packet->uid]) {
spin_unlock_bh(&socket_client->lock);
kfree(socket_packet);
return;
}
list_add_tail(&socket_packet->list, &socket_client->queue_list);
socket_client->queue_len++;
if (socket_client->queue_len > 100) {
socket_packet = list_first_entry(&socket_client->queue_list,
struct socket_packet, list);
list_del(&socket_packet->list);
kfree(socket_packet);
socket_client->queue_len--;
}
spin_unlock_bh(&socket_client->lock);
wake_up(&socket_client->queue_wait);
}
void bat_socket_receive_packet(struct icmp_packet_rr *icmp_packet,
size_t icmp_len)
{
struct socket_client *hash = socket_client_hash[icmp_packet->uid];
if (hash)
bat_socket_add_packet(hash, icmp_packet, icmp_len);
}