fix use-after-free in ds.c
PATCH 1/1 fix use-after-free in ds.c
fixes a use-after-free in ds.c seen when doing:
# cardctl eject # rmmod yenta_socket # killall cardmgr
this is russell king's patch from october plus removes some unused vars. (original see: http://lists.infradead.org/pipermail/linux-pcmcia/2003-October/000303.html )
against 2.6.0, please enqueue.
drivers/pcmcia/ds.c +52 -26
@@ -51,6 +51,8 @@#include <linux/list.h> #include <linux/workqueue.h> +#include <asm/atomic.h> +#include <pcmcia/version.h> #include <pcmcia/cs_types.h> #include <pcmcia/cs.h>
@@ -95,10 +97,12 @@int event_head, event_tail;event_t event[MAX_EVENTS ];struct user_info_t *next; +struct pcmcia_bus_socket *socket; } user_info_t;/* Socket state information */ struct pcmcia_bus_socket { +atomic_t refcount;client_handle_t handle;int state;user_info_t *user;
@@ -113,6 +117,7 @@#define SOCKET_PRESENT 0x01#define SOCKET_BUSY 0x02#define SOCKET_REMOVAL_PENDING 0x10 +#define SOCKET_DEAD 0x80/*====================================================================*/
@@ -137,6 +142,24 @@static struct pcmcia_driver *get_pcmcia_driver (dev_info_t *dev_info);static struct pcmcia_bus_socket *get_socket_info_by_nr (unsigned int nr); +static void pcmcia_put_bus_socket (struct pcmcia_bus_socket *s) +{ +if (atomic_dec_and_test (&s->refcount)) +kfree (s); +} + +static struct pcmcia_bus_socket *pcmcia_get_bus_socket (int nr) +{ +struct pcmcia_bus_socket *s; + + s =get_socket_info_by_nr (nr); +if (s) { +WARN_ON (atomic_read (&s->refcount) ==0 ); +atomic_inc (&s->refcount); + } +return s; +} +/** * pcmcia_register_driver - register a PCMCIA driver with the bus core *
@@ -501,7 +524,7 @@DEBUG (0 ,"ds_open(socket %d)\n" , i); - s =get_socket_info_by_nr (i); + s =pcmcia_get_bus_socket (i);if (!s)return -ENODEV ;
@@ -517,6 +540,7 @@ user->event_tail = user->event_head =0 ; user->next = s->user; user->user_magic =USER_MAGIC ; + user->socket = s; s->user = user; file->private_data = user;
@@ -529,20 +553,17 @@static int ds_release (struct inode *inode,struct file *file) { -socket_t i =iminor (inode);struct pcmcia_bus_socket *s;user_info_t *user, **link;DEBUG (0 ,"ds_release(socket %d)\n" , i); - s =get_socket_info_by_nr (i); -if (!s) -return 0 ; - user = file->private_data;if (CHECK_USER (user)) goto out; + s = user->socket; +/* Unlink user data structure */ if ((file->f_flags &O_ACCMODE ) !=O_RDONLY ) s->state &= ~SOCKET_BUSY ;
@@ -554,6 +575,7 @@ *link = user->next; user->user_magic =0 ;kfree (user); +pcmcia_put_bus_socket (s); out:return 0 ; }/* ds_release */
@@ -563,7 +585,6 @@static ssize_t ds_read (struct file *file,char *buf,size_t count,loff_t *ppos) { -socket_t i =iminor (file->f_dentry->d_inode);struct pcmcia_bus_socket *s;user_info_t *user;
@@ -572,14 +593,14 @@if (count <4 )return -EINVAL ; - s =get_socket_info_by_nr (i); -if (!s) -return -ENODEV ; - user = file->private_data;if (CHECK_USER (user))return -EIO ; + s = user->socket; +if (s->state &SOCKET_DEAD ) +return -EIO ; +if (queue_empty (user)) {interruptible_sleep_on (&s->queue);if (signal_pending (current))
@@ -594,7 +615,6 @@static ssize_t ds_write (struct file *file,const char *buf,size_t count,loff_t *ppos) { -socket_t i =iminor (file->f_dentry->d_inode);struct pcmcia_bus_socket *s;user_info_t *user;
@@ -605,14 +625,14 @@if ((file->f_flags &O_ACCMODE ) ==O_RDONLY )return -EBADF ; - s =get_socket_info_by_nr (i); -if (!s) -return -ENODEV ; - user = file->private_data;if (CHECK_USER (user))return -EIO ; + s = user->socket; +if (s->state &SOCKET_DEAD ) +return -EIO ; +if (s->req_pending) { s->req_pending--;get_user (s->req_result, (int *)buf);
@@ -629,19 +649,19 @@/* No kernel lock - fine */ static u_int ds_poll (struct file *file,poll_table *wait) { -socket_t i =iminor (file->f_dentry->d_inode);struct pcmcia_bus_socket *s;user_info_t *user;DEBUG (2 ,"ds_poll(socket %d)\n" , i); - s =get_socket_info_by_nr (i); -if (!s) -return POLLERR ; - user = file->private_data;if (CHECK_USER (user))return POLLERR ; + s = user->socket; +/* +* We don't check for a dead socket here since that +* will send cardmgr into an endless spin. +*/ poll_wait (file, &s->queue, wait);if (!queue_empty (user))return POLLIN |POLLRDNORM ;
@@ -653,17 +673,21 @@static int ds_ioctl (struct inode * inode,struct file * file,u_int cmd,u_long arg) { -socket_t i =iminor (inode);struct pcmcia_bus_socket *s;u_int size;int ret, err;ds_ioctl_arg_t buf; +user_info_t *user;DEBUG (2 ,"ds_ioctl(socket %d, %#x, %#lx)\n" , i, cmd, arg); - s =get_socket_info_by_nr (i); -if (!s) -return -ENODEV ; + user = file->private_data; +if (CHECK_USER (user)) +return -EIO ; + + s = user->socket; +if (s->state &SOCKET_DEAD ) +return -EIO ; size = (cmd &IOCSIZE_MASK ) >>IOCSIZE_SHIFT ;if (size >sizeof (ds_ioctl_arg_t))return -EINVAL ;
@@ -833,6 +857,7 @@if (!s)return -ENOMEM ;memset (s,0 ,sizeof (struct pcmcia_bus_socket )); +atomic_set (&s->refcount,1 );/* * Ugly. But we want to wait for the socket threads to have started up.
@@ -894,7 +919,8 @@pcmcia_deregister_client (socket->pcmcia->handle); -kfree (socket->pcmcia); + socket->pcmcia->state |=SOCKET_DEAD ; +pcmcia_put_bus_socket (socket->pcmcia); socket->pcmcia =NULL ;return ;
Quoted 1 line · Daniel Ritz
this is russell king's patch from october plus removes some unused vars.
Note that they're only unused if you don't enable debugging.
Quoted 1 line
against 2.6.0, please enqueue.
I think my original patch is already queued, but unfortunately 2.6 isn't going anywhere just yet.
Signature · 4 lines
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 PCMCIA - http://pcmcia.arm.linux.org.uk/
2.6 Serial core