[PATCH] media: az6007: fix WARNING in az6007_i2c_xfer from mutex reinitialization

Zhang Shurong posted 1 patch 2 weeks, 6 days ago
drivers/media/usb/dvb-usb-v2/az6007.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
[PATCH] media: az6007: fix WARNING in az6007_i2c_xfer from mutex reinitialization
Posted by Zhang Shurong 2 weeks, 6 days ago
az6007_power_ctrl() calls mutex_init(&state->mutex) every time it takes
the !state->warm branch. When a control transfer fails before the device
is marked warm, the probe cleanup path invokes az6007_power_ctrl(d, 0),
which re-enters that branch and initializes the same mutex a second time.

That second mutex_init() can race with an I2C transfer. Once
dvb_usbv2_i2c_init() has registered the i2c adapter, userspace may open
/dev/i2c-N and hold state->mutex inside az6007_i2c_xfer() while blocked
in usb_control_msg(). Initializing a locked mutex clears its owner, so
the mutex_unlock() in az6007_i2c_xfer() then trips:

  DEBUG_LOCKS_WARN_ON(__owner_task(owner) != get_current())
  WARNING: kernel/locking/mutex.c at __mutex_unlock_slowpath

Move the mutex_init() into the driver's probe callback. The callback runs
once per probe, immediately after private data allocation and before
identify_state() and i2c adapter registration, at which point no user of
state->mutex can exist. This mirrors mxl111sf_probe(), which initializes
its state lock in the same callback.

Fixes: a2c35d346d9e ("[media] az6007: Protect read/write calls with a mutex")
Cc: stable@vger.kernel.org
Reported-by: syzbot+4ac6df95b7f516179c07d6b8fcd77d81ec45e7f3@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?id=4ac6df95b7f516179c07d6b8fcd77d81ec45e7f3
Signed-off-by: Zhang Shurong <zhang_shurong@foxmail.com>
---
 drivers/media/usb/dvb-usb-v2/az6007.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/drivers/media/usb/dvb-usb-v2/az6007.c b/drivers/media/usb/dvb-usb-v2/az6007.c
index 65ef045b74ca..60af125ae6dd 100644
--- a/drivers/media/usb/dvb-usb-v2/az6007.c
+++ b/drivers/media/usb/dvb-usb-v2/az6007.c
@@ -694,8 +694,6 @@ static int az6007_power_ctrl(struct dvb_usb_device *d, int onoff)
 	pr_debug("%s()\n", __func__);
 
 	if (!state->warm) {
-		mutex_init(&state->mutex);
-
 		ret = az6007_write(d, AZ6007_POWER, 0, 2, NULL, 0);
 		if (ret < 0)
 			return ret;
@@ -889,12 +887,22 @@ static int az6007_download_firmware(struct dvb_usb_device *d,
 	return cypress_load_firmware(d->udev, fw, CYPRESS_FX2);
 }
 
+static int az6007_probe(struct dvb_usb_device *d)
+{
+	struct az6007_device_state *state = d_to_priv(d);
+
+	mutex_init(&state->mutex);
+
+	return 0;
+}
+
 /* DVB USB Driver stuff */
 static struct dvb_usb_device_properties az6007_props = {
 	.driver_name         = KBUILD_MODNAME,
 	.owner               = THIS_MODULE,
 	.firmware            = AZ6007_FIRMWARE,
 
+	.probe               = az6007_probe,
 	.adapter_nr          = adapter_nr,
 	.size_of_priv        = sizeof(struct az6007_device_state),
 	.i2c_algo            = &az6007_i2c_algo,
@@ -917,6 +925,7 @@ static struct dvb_usb_device_properties az6007_cablestar_hdci_props = {
 	.owner               = THIS_MODULE,
 	.firmware            = AZ6007_FIRMWARE,
 
+	.probe               = az6007_probe,
 	.adapter_nr          = adapter_nr,
 	.size_of_priv        = sizeof(struct az6007_device_state),
 	.i2c_algo            = &az6007_i2c_algo,
-- 
2.39.5