i2c: dev: fix blocked adapter deregistration
Subsystems (3)
cover i2c: dev: fix blocked adapter deregistration
As part of addressing i2c lifetime issues, this series fixes an issue which may actually matter to some people, at least if they are playing around with USB attached i2c controllers, and whose fix does not depend on any larger rework.
This could possibly go into 7.3, but can also wait for 7.4 as the impact of the issue (both in terms of number of affected users and symptoms) should be limited.
Johan
Johan Hovold (3):
i2c: dev: fix blocked adapter deregistration
i2c: dev: drop unnecessary sysfs device lookup
i2c: dev: clean up registration
drivers/i2c/i2c-dev.c | 155 +++++++++++++++++++++++++----------------- 1 file changed, 94 insertions(+), 61 deletions(-)
Signature · 1 line
2.54.0
PATCH 1/3 i2c: dev: fix blocked adapter deregistration
The i2c subsystem allows controllers to be used by non-child devices that may remain registered after an adapter goes away.
To handle this, adapter deregistration blocks until the last reference to the adapter is released. Albeit unorthodox, this is mostly fine for the vast majority of controllers but can cause some trouble when controllers reside on hotpluggable buses.
Specifically, userspace can prevent an adapter from being deregistered indefinitely by holding an i2c-dev character device file open. And with USB attached controllers this prevents further hub events from being processed by the parent hub until the file is closed.
Fix the i2c-dev implementation by dropping the additional reference taken at open() and using an rwsem to make sure the adapter is only accessed while registered.
Note that before commit 611e12ea0f12 ("i2c: core: manage i2c bus device refcount in i2c_[get|put]_adapter") an adapter going away would instead have resulted in a use-after-free.
--- drivers/i2c/i2c-dev.c | 97 +++++++++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 23 deletions(-)
drivers/i2c/i2c-dev.c +74 -23
@@ -28,6 +28,7 @@#include <linux/list.h> #include <linux/module.h> #include <linux/notifier.h> +#include <linux/rwsem.h> #include <linux/slab.h> #include <linux/uaccess.h>
@@ -41,11 +42,17 @@*/ struct i2c_dev {struct list_head list; +struct rw_semaphore rwsem;struct i2c_adapter *adap;struct device dev;struct cdev cdev; }; +struct i2c_dev_data { +struct i2c_dev *i2c_dev; +struct i2c_client client; +}; +#define I2C_MINORS (MINORMASK + 1)static LIST_HEAD (i2c_dev_list);static DEFINE_SPINLOCK (i2c_dev_list_lock);
@@ -90,8 +97,14 @@ static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)spin_lock (&i2c_dev_list_lock);list_del (&i2c_dev->list);spin_unlock (&i2c_dev_list_lock); -if (del_cdev) +if (del_cdev) {cdev_device_del (&i2c_dev->cdev, &i2c_dev->dev); + + scoped_guard(rwsem_write, &i2c_dev->rwsem) { + i2c_dev->adap =NULL ; + } + } +put_device (&i2c_dev->dev); }
@@ -134,10 +147,16 @@ ATTRIBUTE_GROUPS(i2c);static ssize_t i2cdev_read (struct file *file,char __user *buf,size_t count,loff_t *offset) { +struct i2c_dev_data *data = file->private_data; +struct i2c_client *client = &data->client; +struct i2c_dev *i2c_dev = data->i2c_dev;char *tmp;int ret; -struct i2c_client *client = file->private_data; +guard (rwsem_read)(&i2c_dev->rwsem); + +if (!i2c_dev->adap) +return -ENODEV ;/* Adapter must support I2C transfers */ if (!i2c_check_functionality (client->adapter,I2C_FUNC_I2C ))
@@ -163,9 +182,16 @@ static ssize_t i2cdev_read(struct file *file, char __user *buf, size_t count,static ssize_t i2cdev_write (struct file *file,const char __user *buf,size_t count,loff_t *offset) { +struct i2c_dev_data *data = file->private_data; +struct i2c_client *client = &data->client; +struct i2c_dev *i2c_dev = data->i2c_dev;int ret;char *tmp; -struct i2c_client *client = file->private_data; + +guard (rwsem_read)(&i2c_dev->rwsem); + +if (!i2c_dev->adap) +return -ENODEV ;/* Adapter must support I2C transfers */ if (!i2c_check_functionality (client->adapter,I2C_FUNC_I2C ))
@@ -397,11 +423,15 @@ static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,return res; } -static long i2cdev_ioctl (struct file *file,unsigned int cmd,unsigned long arg) +static long i2cdev_ioctl_locked (struct file *file,unsigned int cmd,unsigned long arg) { -struct i2c_client *client = file->private_data; +struct i2c_dev_data *data = file->private_data; +struct i2c_client *client = &data->client; +struct i2c_dev *i2c_dev = data->i2c_dev;unsigned long funcs; +lockdep_assert_held (&i2c_dev->rwsem); +dev_dbg (&client->adapter->dev,"ioctl, cmd=0x%02x, arg=0x%02lx\n" , cmd, arg);
@@ -507,6 +537,19 @@ static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)return 0 ; } +static long i2cdev_ioctl (struct file *file,unsigned int cmd,unsigned long arg) +{ +struct i2c_dev_data *data = file->private_data; +struct i2c_dev *i2c_dev = data->i2c_dev; + +guard (rwsem_read)(&i2c_dev->rwsem); + +if (!i2c_dev->adap) +return -ENODEV ; + +return i2cdev_ioctl_locked (file, cmd, arg); +} +#ifdef CONFIG_COMPAT struct i2c_smbus_ioctl_data32 {
@@ -530,8 +573,16 @@ struct i2c_rdwr_ioctl_data32 {static long compat_i2cdev_ioctl (struct file *file,unsigned int cmd,unsigned long arg) { -struct i2c_client *client = file->private_data; +struct i2c_dev_data *data = file->private_data; +struct i2c_client *client = &data->client; +struct i2c_dev *i2c_dev = data->i2c_dev;unsigned long funcs; + +guard (rwsem_read)(&i2c_dev->rwsem); + +if (!i2c_dev->adap) +return -ENODEV ; +switch (cmd) {case I2C_FUNCS : funcs =i2c_get_functionality (client->adapter);
@@ -588,7 +639,7 @@ static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned lo compat_ptr(data32.data)); }default : -return i2cdev_ioctl (file, cmd, arg); +return i2cdev_ioctl_locked (file, cmd, arg); } }#else
@@ -597,13 +648,10 @@ static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned lostatic int i2cdev_open (struct inode *inode,struct file *file) { -unsigned int minor =iminor (inode); + struct i2c_dev *i2c_dev = container_of(inode->i_cdev, struct i2c_dev, cdev); +struct i2c_adapter *adap = i2c_dev->adap; +struct i2c_dev_data *data;struct i2c_client *client; -struct i2c_adapter *adap; - - adap =i2c_get_adapter (minor); -if (!adap) -return -ENODEV ;/* This creates an anonymous i2c_client, which may later be * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
@@ -612,26 +660,27 @@ static int i2cdev_open(struct inode *inode, struct file *file)* or I2C core code!! It just holds private copies of addressing * information and maybe a PEC flag. */ - client =kzalloc_obj (*client); -if (!client) { -i2c_put_adapter (adap); + data =kzalloc_obj (*data); +if (!data)return -ENOMEM ; - } + + data->i2c_dev = i2c_dev; + + client = &data->client; +snprintf (client->name,I2C_NAME_SIZE ,"i2c-dev %d" , adap->nr); client->adapter = adap; - file->private_data = client; + file->private_data = data;return 0 ; }static int i2cdev_release (struct inode *inode,struct file *file) { -struct i2c_client *client = file->private_data; +struct i2c_dev_data *data = file->private_data; -i2c_put_adapter (client->adapter); -kfree (client); - file->private_data =NULL ; +kfree (data);return 0 ; }
@@ -675,8 +724,10 @@ static int i2cdev_attach_adapter(struct device *dev)if (IS_ERR (i2c_dev))return NOTIFY_DONE ; +init_rwsem (&i2c_dev->rwsem); +cdev_init (&i2c_dev->cdev, &i2cdev_fops); - i2c_dev->cdev.owner =THIS_MODULE ; + i2c_dev->cdev.owner = adap->owner;device_initialize (&i2c_dev->dev); i2c_dev->dev.devt =MKDEV (I2C_MAJOR , adap->nr);
Signature · 1 line
2.54.0
Quoted 27 lines · Johan Hovold
The i2c subsystem allows controllers to be used by non-child devices that may remain registered after an adapter goes away.
To handle this, adapter deregistration blocks until the last reference to the adapter is released. Albeit unorthodox, this is mostly fine for the vast majority of controllers but can cause some trouble when controllers reside on hotpluggable buses.
Specifically, userspace can prevent an adapter from being deregistered indefinitely by holding an i2c-dev character device file open. And with USB attached controllers this prevents further hub events from being processed by the parent hub until the file is closed.
Fix the i2c-dev implementation by dropping the additional reference taken at
open()and using an rwsem to make sure the adapter is only accessed while registered.Note that before commit
611e12ea0f12("i2c: core: manage i2c bus device refcount in i2c_[get|put]_adapter") an adapter going away would instead have resulted in a use-after-free.Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")Signed-off-by Johan Hovold <[email protected]>static int
i2cdev_open(struct inode *inode, struct file *file){
drivers/i2c/i2c-dev.c static long compat_i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned lo · line 652
Quoted 3 lines
drivers/i2c/i2c-dev.c +2
-1-unsigned int minor =iminor (inode); + struct i2c_dev *i2c_dev = container_of(inode->i_cdev, struct i2c_dev, cdev); +struct i2c_adapter *adap = i2c_dev->adap;
Bah, I of course need to take the rwsem here as well as pointed out by Sashiko.
Let me respin.
Johan
PATCH 2/3 i2c: dev: drop unnecessary sysfs device lookup
In reply to Johan HovoldThe sysfs lifetime rules guarantees that the i2c-dev class device is still valid while its attribute callbacks are executing so drop the unnecessary reverse lookup.
--- drivers/i2c/i2c-dev.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-)
drivers/i2c/i2c-dev.c +3 -5
@@ -47,6 +47,7 @@ struct i2c_dev {struct device dev;struct cdev cdev; }; +#define to_i2c_dev (d) container_of((d), struct i2c_dev, dev)struct i2c_dev_data {struct i2c_dev *i2c_dev;
@@ -111,10 +112,8 @@ static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)static ssize_t name_show (struct device *dev,struct device_attribute *attr,char *buf) { -struct i2c_dev *i2c_dev =i2c_dev_get_by_minor (MINOR (dev->devt)); +struct i2c_dev *i2c_dev =to_i2c_dev (dev); -if (!i2c_dev) -return -ENODEV ;return sysfs_emit (buf,"%s\n" , i2c_dev->adap->name); }static DEVICE_ATTR_RO (name);
@@ -704,9 +703,8 @@ static const struct class i2c_dev_class = {static void i2cdev_dev_release (struct device *dev) { -struct i2c_dev *i2c_dev; +struct i2c_dev *i2c_dev =to_i2c_dev (dev); - i2c_dev = container_of(dev, struct i2c_dev, dev);kfree (i2c_dev); }
Signature · 1 line
2.54.0
PATCH 3/3 i2c: dev: clean up registration
In reply to Johan HovoldDrop the get_free_i2c_dev() and put_i2c_dev() helpers and do all set up and tear down directly in i2cdev_attach_adapter() and i2cdev_detach_adapter() for consistency and to make the logic clearer.
Note that the device list is only used at detach so the i2c-dev can be added after registering the class device.
--- drivers/i2c/i2c-dev.c | 64 ++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 40 deletions(-)
drivers/i2c/i2c-dev.c +24 -40
@@ -73,42 +73,6 @@ static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)return i2c_dev; } -static struct i2c_dev *get_free_i2c_dev (struct i2c_adapter *adap) -{ -struct i2c_dev *i2c_dev; - -if (adap->nr >=I2C_MINORS ) { -pr_err ("Out of device minors (%d)\n" , adap->nr); -return ERR_PTR (-ENODEV ); - } - - i2c_dev =kzalloc_obj (*i2c_dev); -if (!i2c_dev) -return ERR_PTR (-ENOMEM ); - i2c_dev->adap = adap; - -spin_lock (&i2c_dev_list_lock); -list_add_tail (&i2c_dev->list, &i2c_dev_list); -spin_unlock (&i2c_dev_list_lock); -return i2c_dev; -} - -static void put_i2c_dev (struct i2c_dev *i2c_dev,bool del_cdev) -{ -spin_lock (&i2c_dev_list_lock); -list_del (&i2c_dev->list); -spin_unlock (&i2c_dev_list_lock); -if (del_cdev) { -cdev_device_del (&i2c_dev->cdev, &i2c_dev->dev); - - scoped_guard(rwsem_write, &i2c_dev->rwsem) { - i2c_dev->adap =NULL ; - } - } - -put_device (&i2c_dev->dev); -} -static ssize_t name_show (struct device *dev,struct device_attribute *attr,char *buf) {
@@ -718,11 +682,17 @@ static int i2cdev_attach_adapter(struct device *dev)return NOTIFY_DONE ; adap =to_i2c_adapter (dev); - i2c_dev =get_free_i2c_dev (adap); -if (IS_ERR (i2c_dev)) +if (adap->nr >=I2C_MINORS ) { +pr_err ("Out of device minors (%d)\n" , adap->nr); +return NOTIFY_DONE ; + } + + i2c_dev =kzalloc_obj (*i2c_dev); +if (!i2c_dev)return NOTIFY_DONE ;init_rwsem (&i2c_dev->rwsem); + i2c_dev->adap = adap;cdev_init (&i2c_dev->cdev, &i2cdev_fops); i2c_dev->cdev.owner = adap->owner;
@@ -741,11 +711,15 @@ static int i2cdev_attach_adapter(struct device *dev)if (res) goto err_put_i2c_dev; +spin_lock (&i2c_dev_list_lock); +list_add_tail (&i2c_dev->list, &i2c_dev_list); +spin_unlock (&i2c_dev_list_lock); +pr_debug ("adapter [%s] registered as minor %d\n" , adap->name, adap->nr);return NOTIFY_OK ; err_put_i2c_dev: -put_i2c_dev (i2c_dev, false); +put_device (&i2c_dev->dev);return NOTIFY_DONE ; }
@@ -762,7 +736,17 @@ static int i2cdev_detach_adapter(struct device *dev)if (!i2c_dev)/* attach_adapter must have failed */ return NOTIFY_DONE ; -put_i2c_dev (i2c_dev, true); +spin_lock (&i2c_dev_list_lock); +list_del (&i2c_dev->list); +spin_unlock (&i2c_dev_list_lock); + +cdev_device_del (&i2c_dev->cdev, &i2c_dev->dev); + + scoped_guard(rwsem_write, &i2c_dev->rwsem) { + i2c_dev->adap =NULL ; + } + +put_device (&i2c_dev->dev);pr_debug ("adapter [%s] unregistered\n" , adap->name);return NOTIFY_OK ;
Signature · 1 line
2.54.0