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>
This commit is contained in:
@@ -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->wait);
|
||||||
init_waitqueue_head(&client->tx_flush_wait);
|
init_waitqueue_head(&client->tx_flush_wait);
|
||||||
INIT_LIST_HEAD(&client->phy_receiver_link);
|
INIT_LIST_HEAD(&client->phy_receiver_link);
|
||||||
|
INIT_LIST_HEAD(&client->link);
|
||||||
kref_init(&client->kref);
|
kref_init(&client->kref);
|
||||||
|
|
||||||
file->private_data = client;
|
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);
|
return nonseekable_open(inode, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,15 +448,20 @@ static int ioctl_get_info(struct client *client, union ioctl_arg *arg)
|
|||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
|
mutex_lock(&client->device->client_list_mutex);
|
||||||
|
|
||||||
client->bus_reset_closure = a->bus_reset_closure;
|
client->bus_reset_closure = a->bus_reset_closure;
|
||||||
if (a->bus_reset != 0) {
|
if (a->bus_reset != 0) {
|
||||||
fill_bus_reset_event(&bus_reset, client);
|
fill_bus_reset_event(&bus_reset, client);
|
||||||
if (copy_to_user(u64_to_uptr(a->bus_reset),
|
ret = copy_to_user(u64_to_uptr(a->bus_reset),
|
||||||
&bus_reset, sizeof(bus_reset)))
|
&bus_reset, sizeof(bus_reset));
|
||||||
return -EFAULT;
|
|
||||||
}
|
}
|
||||||
|
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,
|
static int add_client_resource(struct client *client,
|
||||||
|
|||||||
@@ -475,6 +475,9 @@ union fw_cdev_event {
|
|||||||
* of the bus. This does not cause a bus reset to happen.
|
* 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
|
* @bus_reset_closure: Value of &closure in this and subsequent bus reset events
|
||||||
* @card: The index of the card this device belongs to
|
* @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 {
|
struct fw_cdev_get_info {
|
||||||
__u32 version;
|
__u32 version;
|
||||||
|
|||||||
Reference in New Issue
Block a user