diff options
| author | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2011-07-09 16:43:22 +0200 | 
|---|---|---|
| committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2011-07-16 07:24:32 +0200 | 
| commit | 93b37905f70083d6143f5f4dba0a45cc64379a62 (patch) | |
| tree | b6917581cd390ed9967e1df9922940362ad4309e | |
| parent | d873d794235efa590ab3c94d5ee22bb1fab19ac4 (diff) | |
| download | olio-linux-3.10-93b37905f70083d6143f5f4dba0a45cc64379a62.tar.xz olio-linux-3.10-93b37905f70083d6143f5f4dba0a45cc64379a62.zip  | |
firewire: cdev: prevent race between first get_info ioctl and bus reset event queuing
Between open(2) of a /dev/fw* and the first FW_CDEV_IOC_GET_INFO
ioctl(2) on it, the kernel already queues FW_CDEV_EVENT_BUS_RESET events
to be read(2) by the client.  The get_info ioctl is practically always
issued right away after open, hence this condition only occurs if the
client opens during a bus reset, especially during a rapid series of bus
resets.
The problem with this condition is twofold:
  - These bus reset events carry the (as yet undocumented) @closure
    value of 0.  But it is not the kernel's place to choose closures;
    they are privat to the client.  E.g., this 0 value forced from the
    kernel makes it unsafe for clients to dereference it as a pointer to
    a closure object without NULL pointer check.
  - It is impossible for clients to determine the relative order of bus
    reset events from get_info ioctl(2) versus those from read(2),
    except in one way:  By comparison of closure values.  Again, such a
    procedure imposes complexity on clients and reduces freedom in use
    of the bus reset closure.
So, change the ABI to suppress queuing of bus reset events before the
first FW_CDEV_IOC_GET_INFO ioctl was issued by the client.
Note, this ABI change cannot be version-controlled.  The kernel cannot
distinguish old from new clients before the first FW_CDEV_IOC_GET_INFO
ioctl.
We will try to back-merge this change into currently maintained stable/
longterm series, and we only document the new behaviour.  The old
behavior is now considered a kernel bug, which it basically is.
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
Cc: <stable@kernel.org>
| -rw-r--r-- | drivers/firewire/core-cdev.c | 18 | ||||
| -rw-r--r-- | include/linux/firewire-cdev.h | 3 | 
2 files changed, 13 insertions, 8 deletions
diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index 9b5915ebeb3..e6ad3bb6c1a 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -253,14 +253,11 @@ static int fw_device_op_open(struct inode *inode, struct file *file)  	init_waitqueue_head(&client->wait);  	init_waitqueue_head(&client->tx_flush_wait);  	INIT_LIST_HEAD(&client->phy_receiver_link); +	INIT_LIST_HEAD(&client->link);  	kref_init(&client->kref);  	file->private_data = client; -	mutex_lock(&device->client_list_mutex); -	list_add_tail(&client->link, &device->client_list); -	mutex_unlock(&device->client_list_mutex); -  	return nonseekable_open(inode, file);  } @@ -451,15 +448,20 @@ static int ioctl_get_info(struct client *client, union ioctl_arg *arg)  	if (ret != 0)  		return -EFAULT; +	mutex_lock(&client->device->client_list_mutex); +  	client->bus_reset_closure = a->bus_reset_closure;  	if (a->bus_reset != 0) {  		fill_bus_reset_event(&bus_reset, client); -		if (copy_to_user(u64_to_uptr(a->bus_reset), -				 &bus_reset, sizeof(bus_reset))) -			return -EFAULT; +		ret = copy_to_user(u64_to_uptr(a->bus_reset), +				   &bus_reset, sizeof(bus_reset));  	} +	if (ret == 0 && list_empty(&client->link)) +		list_add_tail(&client->link, &client->device->client_list); -	return 0; +	mutex_unlock(&client->device->client_list_mutex); + +	return ret ? -EFAULT : 0;  }  static int add_client_resource(struct client *client, diff --git a/include/linux/firewire-cdev.h b/include/linux/firewire-cdev.h index 4ff09889c5c..55814aa33be 100644 --- a/include/linux/firewire-cdev.h +++ b/include/linux/firewire-cdev.h @@ -475,6 +475,9 @@ union fw_cdev_event {   *		of the bus.  This does not cause a bus reset to happen.   * @bus_reset_closure: Value of &closure in this and subsequent bus reset events   * @card:	The index of the card this device belongs to + * + * As a side effect, reception of %FW_CDEV_EVENT_BUS_RESET events to be read(2) + * is started by this ioctl.   */  struct fw_cdev_get_info {  	__u32 version;  |