Re: [PATCH v2 2/2] i2c: dev: don't allow user-space to deadlock the kernel

Bartosz Golaszewski
2023-01-18T13:24:50Z

On Tue, Jan 17, 2023 at 7:57 PM Wolfram Sang <wsa@kernel.org> wrote:
>
> Hi Bartosz,
>
> > If we open an i2c character device and then unbind the underlying i2c
> > adapter (either by unbinding it manually via sysfs or - for a real-life
> > example - when unplugging a USB device with an i2c adaper), the kernel
> > thread calling i2c_del_adapter() will become blocked waiting for the
> > completion that only completes once all references to the character
> > device get dropped.
> >
> > In order to fix that, we introduce a couple changes. They need to be
> > part of a single commit in order to preserve bisectability. First, drop
> > the dev_release completion. That removes the risk of a deadlock but
> > we now need to protect the character device structures against NULL
> > pointer dereferences. To that end introduce an rw semaphore. It will
> > protect the dummy i2c_client structure against dropping the adapter from
> > under it. It will be taken for reading by all file_operations callbacks
> > and for writing by the notifier's unbind handler. This way we don't
> > prohibit the syscalls that don't get in each other's way from running
> > concurrently but the adapter will not be unbound before all syscalls
> > return.
> >
> > Finally: upon being notified about an unbind event for the i2c adapter,
> > we take the lock for writing and set the adapter pointer in the character
> > device's structure to NULL. This "numbs down" the device - it still exists
> > but is no longer functional. Meanwhile every syscall callback checks that
> > pointer after taking the lock but before executing any code that requires
> > it. If it's NULL, we return an error to user-space.
> >
> > This way we can safely open an i2c device from user-space, unbind the
> > device without triggering a deadlock and any subsequent system-call for
> > the file descriptor associated with the removed adapter will gracefully
> > fail.
> >
> > Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
>
> First of all, thank you for tackling this problem. It was long overdue
> and I really appreciate that you took the initiative to get it solved.
>
> Here are some review comments already. I'd like to do some more testing.
> So far, everything works nicely. Also, I'd like to invite more people to
> have a look at this code. We really don't want to have a regression
> here, so more eyes are very welcome.
>
>
> > @@ -1713,25 +1715,7 @@ void i2c_del_adapter(struct i2c_adapter *adap)
> >
> > i2c_host_notify_irq_teardown(adap);
> >
> > - /* wait until all references to the device are gone
> > - *
> > - * FIXME: This is old code and should ideally be replaced by an
> > - * alternative which results in decoupling the lifetime of the struct
> > - * device from the i2c_adapter, like spi or netdev do. Any solution
> > - * should be thoroughly tested with DEBUG_KOBJECT_RELEASE enabled!
>
> Have you done this? Debugging with DEBUG_KOBJECT_RELEASE enabled?
>

Yes, I ran a simple test script with this option enabled - nothing
suspicious reported.

> > - */
> > - init_completion(&adap->dev_released);
> > device_unregister(&adap->dev);
> > - wait_for_completion(&adap->dev_released);
>
> So, this is basically the key change. I think you handled the userspace
> part via i2c-dev well. I don't have proof yet, but my gut feeling
> wonders if this is complete. Aren't there maybe sysfs-references as
> well. I need to check.
>
> > -
> > - /* free bus id */
> > - mutex_lock(&core_lock);
> > - idr_remove(&i2c_adapter_idr, adap->nr);
> > - mutex_unlock(&core_lock);
> > -
> > - /* Clear the device structure in case this adapter is ever going to be
> > - added again */
> > - memset(&adap->dev, 0, sizeof(adap->dev));
>
> Any reason you didn't add this to release function above? Reading the
> introducing commit, the old drivers needings this still exist IMO.
> (Yes, they should be converted to use the i2c-mux subsystem, but I don't
> think someone will do this)
>

Good catch, added it back.

> > @@ -44,8 +45,14 @@ struct i2c_dev {
> > struct i2c_adapter *adap;
> > struct device dev;
> > struct cdev cdev;
> > + struct rw_semaphore sem;
>
> I'd like to name it 'rwsem' so it is always super-clear what it is.
>
> > };
> >
> > +static inline struct i2c_dev *to_i2c_dev(struct inode *ino)
>
> I'd also prefer a more specific 'inode_to_i2c_dev' function name.
> Personally, I'd also name the argument 'inode' but I'll leave that to
> you.
>
> > +{
> > + return container_of(ino->i_cdev, struct i2c_dev, cdev);
> > +}
>
> ...
>
> Doesn't the function 'name_show()' also need protection? It dereferences
> i2c_dev->adap.
>

Another good catch.

> So much for now,
>
> Wolfram
>

Thanks, will send a v2 shortly.

Bart