diff options
author | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2011-07-09 10:43:22 -0400 |
---|---|---|
committer | Stefan Richter <stefanr@s5r6.in-berlin.de> | 2011-07-16 01:24:32 -0400 |
commit | 93b37905f70083d6143f5f4dba0a45cc64379a62 (patch) | |
tree | b6917581cd390ed9967e1df9922940362ad4309e /drivers/firewire | |
parent | d873d794235efa590ab3c94d5ee22bb1fab19ac4 (diff) |
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>
Diffstat (limited to 'drivers/firewire')
-rw-r--r-- | drivers/firewire/core-cdev.c | 18 |
1 files changed, 10 insertions, 8 deletions
diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index 9b5915ebeb35..e6ad3bb6c1a6 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) | |||
253 | init_waitqueue_head(&client->wait); | 253 | init_waitqueue_head(&client->wait); |
254 | init_waitqueue_head(&client->tx_flush_wait); | 254 | init_waitqueue_head(&client->tx_flush_wait); |
255 | INIT_LIST_HEAD(&client->phy_receiver_link); | 255 | INIT_LIST_HEAD(&client->phy_receiver_link); |
256 | INIT_LIST_HEAD(&client->link); | ||
256 | kref_init(&client->kref); | 257 | kref_init(&client->kref); |
257 | 258 | ||
258 | file->private_data = client; | 259 | file->private_data = client; |
259 | 260 | ||
260 | mutex_lock(&device->client_list_mutex); | ||
261 | list_add_tail(&client->link, &device->client_list); | ||
262 | mutex_unlock(&device->client_list_mutex); | ||
263 | |||
264 | return nonseekable_open(inode, file); | 261 | return nonseekable_open(inode, file); |
265 | } | 262 | } |
266 | 263 | ||
@@ -451,15 +448,20 @@ static int ioctl_get_info(struct client *client, union ioctl_arg *arg) | |||
451 | if (ret != 0) | 448 | if (ret != 0) |
452 | return -EFAULT; | 449 | return -EFAULT; |
453 | 450 | ||
451 | mutex_lock(&client->device->client_list_mutex); | ||
452 | |||
454 | client->bus_reset_closure = a->bus_reset_closure; | 453 | client->bus_reset_closure = a->bus_reset_closure; |
455 | if (a->bus_reset != 0) { | 454 | if (a->bus_reset != 0) { |
456 | fill_bus_reset_event(&bus_reset, client); | 455 | fill_bus_reset_event(&bus_reset, client); |
457 | if (copy_to_user(u64_to_uptr(a->bus_reset), | 456 | ret = copy_to_user(u64_to_uptr(a->bus_reset), |
458 | &bus_reset, sizeof(bus_reset))) | 457 | &bus_reset, sizeof(bus_reset)); |
459 | return -EFAULT; | ||
460 | } | 458 | } |
459 | if (ret == 0 && list_empty(&client->link)) | ||
460 | list_add_tail(&client->link, &client->device->client_list); | ||
461 | 461 | ||
462 | return 0; | 462 | mutex_unlock(&client->device->client_list_mutex); |
463 | |||
464 | return ret ? -EFAULT : 0; | ||
463 | } | 465 | } |
464 | 466 | ||
465 | static int add_client_resource(struct client *client, | 467 | static int add_client_resource(struct client *client, |