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

From: Wolfram Sang
Date: Tue Jan 17 2023 - 15:07:49 EST


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@xxxxxxxxxx>

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?

> - */
> - 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)

> @@ -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.

So much for now,

Wolfram

Attachment: signature.asc
Description: PGP signature