From nobody Fri Oct 23 22:14:05 2026 Received: from zg8tmtyylji0my4xnjqumte4.icoremail.net (zg8tmtyylji0my4xnjqumte4.icoremail.net [162.243.164.118]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 9794137F74B; Thu, 15 Oct 2026 16:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=162.243.164.118 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792080717; cv=none; b=g38Q9n77B3gdPm24mZQDVAaKoOjr36abaHyn5SJO+0fUeJ6HSgJW8IMO2oVmdP4cDN4Szv+oE7jmQWIigYIUICEKQynUzYXCIWnLafHezlSs/OQq4Bxu9ikTwBZ1ElnunFkIk+e0UwUnChJvGODLi4UMUoWt/pjqhqA9vj8xEsM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792080717; c=relaxed/simple; bh=0dyQ4hQlXgUouOVKa9bWV0guUdmgx6p8arnPiTS4dyU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Y1Ic90XlNOYLIpb/V0JPH3s+14pmKNT28xRTrZfMDPkfX/V4X2XPj4d9aKNtbUbuFRFIbWZIL6/BuaKIB8fTl48qh1jXV1XzW54l+V2hm8y/5/fGDe9t0yfQE1me3ZJ6HDrOl9l1SSpxeKqlGMCMZsIXulFgbx1ydKOQ3DqMnok= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=stu.xidian.edu.cn; spf=pass smtp.mailfrom=stu.xidian.edu.cn; dkim=fail (0-bit key) header.d=stu.xidian.edu.cn header.i=@stu.xidian.edu.cn header.b=UX8dPuCO reason="key not found in DNS"; arc=none smtp.client-ip=162.243.164.118 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=stu.xidian.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=stu.xidian.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=fail reason="key not found in DNS" (0-bit key) header.d=stu.xidian.edu.cn header.i=@stu.xidian.edu.cn header.b="UX8dPuCO" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=stu.xidian.edu.cn; s=dkim; h=Received:From:To:Cc:Subject:Date: Message-Id:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding; bh=timllQBsrVmrZh5n1Ol+pGuc1wlMudyaff 2+nWHQ1dg=; b=UX8dPuCOYCVjfPobjALswg9kyEqQ9Vzgx39UPbjwTWMEME/fL4 9TTMgQie8B3QSFQBJZ4jwYnDHw1pCzxq94HMnUAExuJfveu9f1NHPMV+qrHrnDq6 Fho2gBn0vyXoATWU9Xpg82xoM1bsiRLY3OwCQQY2E1uAHC1S8fWeJUYiE= Received: from user-virtual-machine.localdomain (unknown [113.200.174.59]) by hzbj-edu-front-4.icoremail.net (Coremail) with UTF8SMTPA id BrQMCkAWOwE3+9BqqQmlAA--.58379S3; Fri, 16 Oct 2026 00:11:36 +0800 (CST) From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> To: Mauro Carvalho Chehab Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org, Bin Yu , Cheng Wen , YuanZhe Liu <25031212351@stu.xidian.edu.cn> Subject: [PATCH v2 1/3] media: dvb-core: fix use-after-free in dvb_remove_device() and dvb_device_open() Date: Fri, 16 Oct 2026 00:11:32 +0800 Message-Id: <20261015161134.10425-2-25031212351@stu.xidian.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20261015161134.10425-1-25031212351@stu.xidian.edu.cn> References: <20261015161134.10425-1-25031212351@stu.xidian.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-CM-TRANSID: BrQMCkAWOwE3+9BqqQmlAA--.58379S3 X-Coremail-Antispam: 1UD129KBjvJXoW3AF18tr1DWrW5CF17CFyUZFb_yoW7uF4kpF ZIga45KrWDKr40gr47AF109F98Arnay3yruFWa9r97Kr4fJrySyryvyFWUAr13JrZ7AFyj qry3Gryku3y5Cr7anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUmv14x267AKxVW8JVW5JwAFc2x0x2IEx4CE42xK8VAvwI8IcIk0 rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jr4l82xGYIkIc2 x26xkF7I0E14v26r1I6r4UM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48ve4kI8wA2z4x0 Y4vE2Ix0cI8IcVAFwI0_Jr0_JF4l84ACjcxK6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1l84 ACjcxK6I8E87Iv67AKxVWUJVW8JwA2z4x0Y4vEx4A2jsIEc7CjxVAFwI0_Gr0_Gr1UM2vY z4IE04k24VAvwVAKI4IrM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c 02F40Ex7xfMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm72CE 4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lF7I21c0EjII2zVCS5cI20VAGYxC7Mx kF7I0En4kS14v26r126r1DMxkIecxEwVAFwVWUMxAIw28IcxkI7VAKI48JMxC20s026xCa FVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCjr7xvwVAFwI0_JrI_Jr Wlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r1j 6r1xMIIF0xvE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwCI42IY6xAIw20EY4v20xvaj40_Jr 0_JF4lIxAIcVC2z280aVAFwI0_Jr0_Gr1lIxAIcVC2z280aVCY1x0267AKxVW8JVW8JrUv cSsGvfC2KfnxnUUI43ZEXa7sRiiiStUUUUU== X-CM-SenderInfo: ysvqjiysrsjkur6v33wo0lvxldqovvfxof0/1tbiAgUHD2rQ7rwJiwABsk Content-Type: text/plain; charset="utf-8" dvb_remove_device() clears dvb_minors[] and drops the minors-table reference under minor_rwsem, but then keeps using "dvbdev": down_write(&minor_rwsem); dvb_minors[dvbdev->minor] =3D NULL; dvb_device_put(dvbdev); /* may kfree(dvbdev) */ up_write(&minor_rwsem); dvb_media_device_free(dvbdev); device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor)); list_del(&dvbdev->list_head); If the put() frees the object (e.g. USB disconnect racing with an in-flight open() that still holds a reference of its own, or an open() error path having already dropped the last open() reference), the remaining three statements are use-after-free. KASAN reports the read of dvbdev->minor inside device_destroy(), and the list_del() then writes list poison into the freed object. dvb_unregister_device() has the same shape: it calls dvb_remove_device() and then dvb_device_put(), i.e. it relies on dvb_remove_device() not consuming the caller's reference. Fix both by making the reference accounting symmetric: - dvb_remove_device() no longer touches the kref at all. It removes the minors entry, frees the media resources, destroys the class device and unlinks the object from the adapter list. Everything it needs from "dvbdev" is done while the object is still alive; the caller still owns its reference. - dvb_unregister_device() now drops both references the device actually holds: the minors-table one and the caller's initial one. All in-tree callers that want the object gone already call dvb_unregister_device(), so the extra put() restores the previous net refcount balance while keeping the object alive until after the last use. While at it, close the dvb_device_open() race that supplied the stale pointer in the first place: it fetched dvb_minors[minor] under down_read(&minor_rwsem), but only called dvb_device_get() after a sequence that a concurrent dvb_remove_device() could preempt to free the object. KASAN caught this as a read of ->fops on a freed kmalloc-128 object, together with "refcount_t: addition on 0" warnings on the same path. Do the whole lookup + pin atomically under minor_rwsem: take the kref while still holding the read lock (which excludes the only writer, dvb_remove_device()), and only then call file->f_op->open(). Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> --- drivers/media/dvb-core/dvbdev.c | 58 +++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/drivers/media/dvb-core/dvbdev.c b/drivers/media/dvb-core/dvbde= v.c index d753d329502a..9eb88625c7f6 100644 --- a/drivers/media/dvb-core/dvbdev.c +++ b/drivers/media/dvb-core/dvbdev.c @@ -87,6 +87,8 @@ static int dvb_device_open(struct inode *inode, struct fi= le *file) { struct dvb_device *dvbdev; unsigned int minor =3D iminor(inode); + const struct file_operations *new_fops; + int err =3D 0; =20 if (minor >=3D MAX_DVB_MINORS) return -ENODEV; @@ -94,25 +96,33 @@ static int dvb_device_open(struct inode *inode, struct = file *file) mutex_lock(&dvbdev_mutex); down_read(&minor_rwsem); =20 + /* + * Look the device up and pin it while still holding minor_rwsem. + * dvb_remove_device() takes the write side to clear the entry, so + * once the kref has been taken the object cannot be freed until + * we drop it again. + */ dvbdev =3D dvb_minors[minor]; =20 - if (dvbdev && dvbdev->fops) { - int err =3D 0; - const struct file_operations *new_fops; - - new_fops =3D fops_get(dvbdev->fops); - if (!new_fops) - goto fail; - file->private_data =3D dvb_device_get(dvbdev); - replace_fops(file, new_fops); - if (file->f_op->open) - err =3D file->f_op->open(inode, file); - up_read(&minor_rwsem); - mutex_unlock(&dvbdev_mutex); - if (err) - dvb_device_put(dvbdev); - return err; + if (!dvbdev || !dvbdev->fops) + goto fail; + + /* Pin the device before dropping minor_rwsem. */ + file->private_data =3D dvb_device_get(dvbdev); + new_fops =3D fops_get(dvbdev->fops); + up_read(&minor_rwsem); + mutex_unlock(&dvbdev_mutex); + + if (!new_fops) { + dvb_device_put(dvbdev); + return -ENODEV; } + replace_fops(file, new_fops); + if (file->f_op->open) + err =3D file->f_op->open(inode, file); + if (err) + dvb_device_put(dvbdev); + return err; fail: up_read(&minor_rwsem); mutex_unlock(&dvbdev_mutex); @@ -596,9 +606,16 @@ void dvb_remove_device(struct dvb_device *dvbdev) if (!dvbdev) return; =20 + /* + * Stop new opens first, then tear down everything that still + * needs the object. We must not drop the kref here: our caller + * still owns a reference and keeps using the object after this + * function returns. The minors-table reference is dropped in + * dvb_unregister_device() instead, pairing the dvb_device_get() + * that installed the entry in dvb_register_device(). + */ down_write(&minor_rwsem); dvb_minors[dvbdev->minor] =3D NULL; - dvb_device_put(dvbdev); up_write(&minor_rwsem); =20 dvb_media_device_free(dvbdev); @@ -632,6 +649,13 @@ void dvb_device_put(struct dvb_device *dvbdev) void dvb_unregister_device(struct dvb_device *dvbdev) { dvb_remove_device(dvbdev); + + /* + * Drop both the minors-table reference and the caller's own one. + * The object is only freed once every open() fd has dropped its + * reference too. + */ + dvb_device_put(dvbdev); dvb_device_put(dvbdev); } EXPORT_SYMBOL(dvb_unregister_device); --=20 2.34.1 From nobody Fri Oct 23 22:14:05 2026 Received: from zg8tmtyylji0my4xnjqumte4.icoremail.net (zg8tmtyylji0my4xnjqumte4.icoremail.net [162.243.164.118]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 978483769E6; Thu, 15 Oct 2026 16:11:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=162.243.164.118 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792080715; cv=none; b=UI1wtDnsjjnL1D02hrHX3Lx2pCGPJgj6RZaTKlDacfG7AjZS//tBspWw4oJYm2y4YkUNbsbktNivZnDYEvLp09bZd53+CjpyPRt3y7Cwv/Yk7B/4CXKLdksN3GIiYz61S8Sdo7ZkHOuimJfg4AzdiFuBnI87SyyJPjahSEfF3SM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792080715; c=relaxed/simple; bh=xq2XewVoVjf6bVhh4Tsu6QYfbKWhfGOAXIcFU6Yn0XA=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=aJ0yY94JmJnxmkV2fVw+d92xxUP3OVjVp7mJAISDaN9rhOsgiRQk9dWNGfqyd/12U8dLKi9uDGB5yXw6CdoofpE/J538KbCFFz3OXEtn5wIPWr0P3TlGGnWkzbA2nWJvUXgMeebA3ztqDj80OKRFo5r3GESMfRSlKMwP22/JPGk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=stu.xidian.edu.cn; spf=pass smtp.mailfrom=stu.xidian.edu.cn; dkim=fail (0-bit key) header.d=stu.xidian.edu.cn header.i=@stu.xidian.edu.cn header.b=E6EnWHhM reason="key not found in DNS"; arc=none smtp.client-ip=162.243.164.118 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=stu.xidian.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=stu.xidian.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=fail reason="key not found in DNS" (0-bit key) header.d=stu.xidian.edu.cn header.i=@stu.xidian.edu.cn header.b="E6EnWHhM" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=stu.xidian.edu.cn; s=dkim; h=Received:From:To:Cc:Subject:Date: Message-Id:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding; bh=bPBlrc4qEBfjzmIxVmq6fxazSIS+RETAtE IJfpuzKtI=; b=E6EnWHhMkZXLtBmfUlj6zr8ZieAHyCdQjc2mmgKCLs16BPnxzO D8Rc6WbH6inddQGHR8sO5tafnr1+1+stPbcmdSuiagj75AQfxmj2MSH6Z1CXe4Ro dW6D2wHn0zT6sGOPZ15+0WTA/UO7zXxy6Ssm7qYXgi3/tlsW/rr4pQtmw= Received: from user-virtual-machine.localdomain (unknown [113.200.174.59]) by hzbj-edu-front-4.icoremail.net (Coremail) with UTF8SMTPA id BrQMCkAWOwE3+9BqqQmlAA--.58379S4; Fri, 16 Oct 2026 00:11:37 +0800 (CST) From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> To: Mauro Carvalho Chehab Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org, Bin Yu , Cheng Wen , YuanZhe Liu <25031212351@stu.xidian.edu.cn> Subject: [PATCH v2 2/3] media: dvb-core: keep dvb_device alive while checking users count in frontend release paths Date: Fri, 16 Oct 2026 00:11:33 +0800 Message-Id: <20261015161134.10425-3-25031212351@stu.xidian.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20261015161134.10425-1-25031212351@stu.xidian.edu.cn> References: <20261015161134.10425-1-25031212351@stu.xidian.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-CM-TRANSID: BrQMCkAWOwE3+9BqqQmlAA--.58379S4 X-Coremail-Antispam: 1UD129KBjvJXoWxWF1kJry8CFyrCF4fury8Zrb_yoW5Krykpr Z3JFW5KrWUKw4kKrs7C3W7ZrnYvF4Syr98C34xt3Z7Cr1fX34SkryFq34q9r13Cr93A3yj qwnrGay8C3y7AaDanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUmv14x267AKxVW5JVWrJwAFc2x0x2IEx4CE42xK8VAvwI8IcIk0 rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jryl82xGYIkIc2 x26xkF7I0E14v26r4j6ryUM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48ve4kI8wA2z4x0 Y4vE2Ix0cI8IcVAFwI0_JFI_Gr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1l84 ACjcxK6I8E87Iv67AKxVWUJVW8JwA2z4x0Y4vEx4A2jsIEc7CjxVAFwI0_Gr0_Gr1UM2vY z4IE04k24VAvwVAKI4IrM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c 02F40Ex7xfMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm72CE 4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lF7I21c0EjII2zVCS5cI20VAGYxC7Mx kF7I0En4kS14v26r126r1DMxkIecxEwVAFwVWUMxAIw28IcxkI7VAKI48JMxC20s026xCa FVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCjr7xvwVAFwI0_JrI_Jr Wlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r1j 6r1xMIIF0xvE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwCI42IY6xAIw20EY4v20xvaj40_Jr 0_JF4lIxAIcVC2z280aVAFwI0_Jr0_Gr1lIxAIcVC2z280aVCY1x0267AKxVW8JVW8JrUv cSsGvfC2KfnxnUUI43ZEXa7sR_BMKtUUUUU== X-CM-SenderInfo: ysvqjiysrsjkur6v33wo0lvxldqovvfxof0/1tbiAQUHD2rPmtGsigACsG Content-Type: text/plain; charset="utf-8" Both dvb_frontend_release() and the __dvb_frontend_open() error path inspect dvbdev->users after calling dvb_generic_release(), which ends with dvb_device_put(). If that put() drops the last reference - e.g. the device was unregistered during USB disconnect while this was the last open() file - "dvbdev" is freed before ->users is read: BUG: KASAN: slab-use-after-free Read of size 8 dvb_frontend_release+0x28b/0x2b0 dvb_frontend.c:2903 The same pattern exists at err1: after dvb_generic_release() in __dvb_frontend_open(), the ->users check runs on an object that may already be gone. Take the extra reference *before* dvb_generic_release(): the file still holds its own reference at that point, so the get is guaranteed to be valid, while a get after the put() could itself touch freed memory. Drop the reference again after the ->users check. On the __dvb_frontend_open() error path the reference is deliberately not put() back inside the function. fops->release() does not run for an erroring open(), so dvb_device_open() calls dvb_device_put() itself; that put() drops the reference taken here. This also keeps the accounting balanced: dvb_generic_release() already dropped the file's original reference, and the extra get hands dvb_device_open() one to drop in its place. In dvb_frontend_release() the reference is balanced explicitly, before dvb_frontend_put(fe). Fixes: 0fc044b2b5e2 ("media: dvbdev: adopts refcnt to avoid UAF") Fixes: a2dd235df435 ("media: dvbdev: drop refcount on error path in dvb_dev= ice_open()") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> --- drivers/media/dvb-core/dvb_frontend.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core= /dvb_frontend.c index 0286da57f382..00c29cf7ddf0 100644 --- a/drivers/media/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -2834,6 +2834,14 @@ static int __dvb_frontend_open(struct inode *inode, = struct file *file) mutex_unlock(&fe->dvb->mdev_lock); err2: #endif + /* + * dvb_generic_release() ends with dvb_device_put(), which may + * drop the last reference if the device was unregistered while + * this file was open. Take an extra reference first so the + * ->users check below cannot touch a freed object. + * dvb_device_open() drops it again when ->open() fails. + */ + dvb_device_get(dvbdev); dvb_generic_release(inode, file); err1: if (dvbdev->users =3D=3D -1 && fe->ops.ts_bus_ctrl) @@ -2934,6 +2942,15 @@ static int dvb_frontend_release(struct inode *inode,= struct file *file) mb(); } =20 + /* + * dvb_generic_release() ends with dvb_device_put(), which may + * drop the last reference if the device was unregistered while + * this file was open. Take an extra reference first so the + * ->users check below cannot touch a freed object, and drop it + * again right after. + */ + dvb_device_get(dvbdev); + ret =3D dvb_generic_release(inode, file); =20 if (dvbdev->users =3D=3D -1) { @@ -2954,6 +2971,8 @@ static int dvb_frontend_release(struct inode *inode, = struct file *file) fe->ops.ts_bus_ctrl(fe, 0); } =20 + dvb_device_put(dvbdev); + dvb_frontend_put(fe); =20 return ret; --=20 2.34.1 From nobody Fri Oct 23 22:14:05 2026 Received: from zg8tmtyylji0my4xnjqumte4.icoremail.net (zg8tmtyylji0my4xnjqumte4.icoremail.net [162.243.164.118]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4E45138E5FE; Thu, 15 Oct 2026 16:11:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=162.243.164.118 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792080724; cv=none; b=eqJ8DSKYXepNiQLtBGrxKWpGtKjPXE6U1fft9g87C+XqE6rSxsCZoU6xLy+dfhFlwmEduTm+m1W+jayDWbLtbR1n8iSmQA6/VskxNtVffqOLpFI8x9TMI2nWpjvP3XkaZ282907Gahc6DYUYX0CcBLnBp/vVhrzSL5qNpI2VipY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1792080724; c=relaxed/simple; bh=oOaZB/KrJS+U4ldA4bFNevO3jUMakzIRmYaYXmQDZWU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=rren9kRqx2P8MP60C0i8xTmPR+bWRyUwb8eg5ASpKwDOXS2GA+1mvXD5HTlK4BP3t2YOgcyX9FxtmLGZd9lZmlUupKvkFDrO6PyR/YrFXVN4xwDN4F/wJa98PtLYNx89Ph+Wv3AKsd5581Y2+YaoNNFKKBV2+/qfhffybqPolkw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=stu.xidian.edu.cn; spf=pass smtp.mailfrom=stu.xidian.edu.cn; dkim=fail (0-bit key) header.d=stu.xidian.edu.cn header.i=@stu.xidian.edu.cn header.b=lJblSupF reason="key not found in DNS"; arc=none smtp.client-ip=162.243.164.118 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=stu.xidian.edu.cn Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=stu.xidian.edu.cn Authentication-Results: smtp.subspace.kernel.org; dkim=fail reason="key not found in DNS" (0-bit key) header.d=stu.xidian.edu.cn header.i=@stu.xidian.edu.cn header.b="lJblSupF" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=stu.xidian.edu.cn; s=dkim; h=Received:From:To:Cc:Subject:Date: Message-Id:In-Reply-To:References:MIME-Version: Content-Transfer-Encoding; bh=9YA6tJgduUfXuXbHzBSH6n4F9YgUReV3qi W+Dk7pAC0=; b=lJblSupFyPpwNWlI+b2J4pEOaWIGEZy8jggANZ/Fm1N6iT8CKm SH5wB8Fw1EDYxlifeFrTsj53FvbYxZT5hV7olVH7o6FkyM8z9JS9q+HyozdxK5WV Sx86rEVC1sVB5imsdPt4w5WSu9dBlQdM0KeezuE2dxM9pS5yIY9h9VMWk= Received: from user-virtual-machine.localdomain (unknown [113.200.174.59]) by hzbj-edu-front-4.icoremail.net (Coremail) with UTF8SMTPA id BrQMCkAWOwE3+9BqqQmlAA--.58379S5; Fri, 16 Oct 2026 00:11:38 +0800 (CST) From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> To: Mauro Carvalho Chehab Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org, Bin Yu , Cheng Wen , YuanZhe Liu <25031212351@stu.xidian.edu.cn> Subject: [PATCH v2 3/3] media: dvb-usb: refcount dvb_usb_device to fix disconnect UAF on open chardevs Date: Fri, 16 Oct 2026 00:11:34 +0800 Message-Id: <20261015161134.10425-4-25031212351@stu.xidian.edu.cn> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20261015161134.10425-1-25031212351@stu.xidian.edu.cn> References: <20261015161134.10425-1-25031212351@stu.xidian.edu.cn> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-CM-TRANSID: BrQMCkAWOwE3+9BqqQmlAA--.58379S5 X-Coremail-Antispam: 1UD129KBjvJXoWfGrWrtFyrXF13uF17GFy3Arb_yoWDWw4Dpa 1DKFW5KrWUGrn7Ww4UArn8Xrs5Ga1vka4rKryxGw1agFs3C34UGry8Kry5tr4rG397JFy2 q3WUWryUKF1UGr7anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUmv14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK8VAvwI8IcIk0 rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JrWl82xGYIkIc2 x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48ve4kI8wA2z4x0 Y4vE2Ix0cI8IcVAFwI0_JFI_Gr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1l84 ACjcxK6I8E87Iv67AKxVWUJVW8JwA2z4x0Y4vEx4A2jsIEc7CjxVAFwI0_Gr0_Gr1UM2vY z4IE04k24VAvwVAKI4IrM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c 02F40Ex7xfMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm72CE 4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lF7I21c0EjII2zVCS5cI20VAGYxC7Mx kF7I0En4kS14v26r126r1DMxkIecxEwVAFwVWUMxAIw28IcxkI7VAKI48JMxC20s026xCa FVCjc4AY6r1j6r4UMI8I3I0E5I8CrVAFwI0_Jr0_Jr4lx2IqxVCjr7xvwVAFwI0_JrI_Jr Wlx4CE17CEb7AF67AKxVWUAVWUtwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r1j 6r1xMIIF0xvE2Ix0cI8IcVCY1x0267AKxVW8JVWxJwCI42IY6xAIw20EY4v20xvaj40_Jr 0_JF4lIxAIcVC2z280aVAFwI0_Jr0_Gr1lIxAIcVC2z280aVCY1x0267AKxVW8JVW8JrUv cSsGvfC2KfnxnUUI43ZEXa7VUUU5r7UUUUU== X-CM-SenderInfo: ysvqjiysrsjkur6v33wo0lvxldqovvfxof0/1tbiAgUHD2rQ7rwJjAABsj Content-Type: text/plain; charset="utf-8" On USB unbind (physical disconnect or usbfs USBDEVFS_DISCONNECT_CLAIM / USBDEVFS_RESET / USBDEVFS_SETCONFIGURATION), dvb_usb_device_exit() -> dvb_usb_exit() tears the whole "struct dvb_usb_device" down while userspace may still hold open /dev/dvb/adapter*/frontend* file descriptors and while the kdvb-ad-X-fe-Y thread is still running. The post-disconnect paths then trip over the freed object: * i2c_transfer(&d->i2c_adap) from a frontend's ->release() / ->init() reads i2c_adap.bus_lock etc. out of freed memory (KASAN reports A/D in the bug report); * dvb_usb_generic_rw() / ttusb2_i2c_xfer() lock/unlock &d->usb_mutex / &d->i2c_mutex inside freed memory (KASAN UAF writes, UBSAN qspinlock splats, and a NULL dereference where "d" was gone entirely); * dvb_usb_fe_wakeup() / dvb_usb_fe_sleep() call dvb_usb_device_power_ctrl() -> ttusb2_power_ctrl() on freed "d". struct dvb_usb_device has no refcount, so there is no way to keep it alive until the last chardev user is gone. Add one: * New fields "struct kref kref" and "int dead" in struct dvb_usb_device. * New helpers dvb_usb_device_get() / dvb_usb_device_put(); the release function frees ->priv and "d" once the last reference is dropped. * dvb_usb_device_init() initialises the kref; dvb_usb_exit() no longer frees "d" but marks it ->dead, keeps doing all the subsystem teardown (remote, adapters, i2c, priv_destroy), and drops the framework reference. The object itself is freed by the last put(). * The places that can outlive disconnect now pin the device: - dvb_usb_fe_wakeup() / dvb_usb_fe_sleep() get/put around the power-control + fe_init/fe_sleep calls; - dvb_usb_generic_rw() gets a ref for the duration of the USB I/O; - ttusb2_i2c_xfer() gets a ref around its i2c work. If the device is ->dead they skip the USB I/O and return -ENODEV / the original error path instead of touching a gone udev. Because the kref lives inside the object itself, every consumer that wants to probe "is it dead?" does so while holding a ref, so the flag can never be read from freed memory. With the object kept alive, the mutexes and the i2c_adapter embedded in it are also still valid, which is what the KASAN/UBSAN reports were complaining about. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> --- drivers/media/usb/dvb-usb/dvb-usb-dvb.c | 20 ++++++++++++--- drivers/media/usb/dvb-usb/dvb-usb-init.c | 32 ++++++++++++++++++++++-- drivers/media/usb/dvb-usb/dvb-usb-urb.c | 15 +++++++++-- drivers/media/usb/dvb-usb/dvb-usb.h | 13 ++++++++++ drivers/media/usb/dvb-usb/ttusb2.c | 18 ++++++++++--- 5 files changed, 87 insertions(+), 11 deletions(-) diff --git a/drivers/media/usb/dvb-usb/dvb-usb-dvb.c b/drivers/media/usb/dv= b-usb/dvb-usb-dvb.c index 029dad86a059..e9c15a477103 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb-dvb.c +++ b/drivers/media/usb/dvb-usb/dvb-usb-dvb.c @@ -251,26 +251,38 @@ static int dvb_usb_fe_wakeup(struct dvb_frontend *fe) { struct dvb_usb_adapter *adap =3D fe->dvb->priv; =20 - dvb_usb_device_power_ctrl(adap->dev, 1); + if (!dvb_usb_device_get(adap->dev)) + return -ENODEV; + + if (!adap->dev->dead) + dvb_usb_device_power_ctrl(adap->dev, 1); =20 dvb_usb_set_active_fe(fe, 1); =20 - if (adap->fe_adap[fe->id].fe_init) + if (!adap->dev->dead && adap->fe_adap[fe->id].fe_init) adap->fe_adap[fe->id].fe_init(fe); =20 + dvb_usb_device_put(adap->dev); return 0; } =20 static int dvb_usb_fe_sleep(struct dvb_frontend *fe) { struct dvb_usb_adapter *adap =3D fe->dvb->priv; + int ret =3D 0; =20 - if (adap->fe_adap[fe->id].fe_sleep) + if (!dvb_usb_device_get(adap->dev)) + return -ENODEV; + + if (!adap->dev->dead && adap->fe_adap[fe->id].fe_sleep) adap->fe_adap[fe->id].fe_sleep(fe); =20 dvb_usb_set_active_fe(fe, 0); =20 - return dvb_usb_device_power_ctrl(adap->dev, 0); + if (!adap->dev->dead) + ret =3D dvb_usb_device_power_ctrl(adap->dev, 0); + dvb_usb_device_put(adap->dev); + return ret; } =20 int dvb_usb_adapter_frontend_init(struct dvb_usb_adapter *adap) diff --git a/drivers/media/usb/dvb-usb/dvb-usb-init.c b/drivers/media/usb/d= vb-usb/dvb-usb-init.c index 6d2672bcc69b..3b68d914e647 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb-init.c +++ b/drivers/media/usb/dvb-usb/dvb-usb-init.c @@ -136,9 +136,35 @@ static int dvb_usb_adapter_exit(struct dvb_usb_device = *d) =20 =20 /* general initialization functions */ +static void dvb_usb_free_device(struct kref *ref) +{ + struct dvb_usb_device *d =3D + container_of(ref, struct dvb_usb_device, kref); + + kfree(d->priv); + kfree(d); +} + +struct dvb_usb_device *dvb_usb_device_get(struct dvb_usb_device *d) +{ + if (!d) + return NULL; + kref_get(&d->kref); + return d; +} +EXPORT_SYMBOL(dvb_usb_device_get); + +void dvb_usb_device_put(struct dvb_usb_device *d) +{ + if (d) + kref_put(&d->kref, dvb_usb_free_device); +} +EXPORT_SYMBOL(dvb_usb_device_put); + static int dvb_usb_exit(struct dvb_usb_device *d) { deb_info("state before exiting everything: %x\n", d->state); + d->dead =3D 1; dvb_usb_remote_exit(d); dvb_usb_adapter_exit(d); dvb_usb_i2c_exit(d); @@ -148,8 +174,8 @@ static int dvb_usb_exit(struct dvb_usb_device *d) if (d->priv !=3D NULL && d->props.priv_destroy !=3D NULL) d->props.priv_destroy(d); =20 - kfree(d->priv); - kfree(d); + /* last put() frees the device */ + dvb_usb_device_put(d); return 0; } =20 @@ -157,6 +183,7 @@ static int dvb_usb_init(struct dvb_usb_device *d, short= *adapter_nums) { int ret =3D 0; =20 + kref_init(&d->kref); mutex_init(&d->data_mutex); mutex_init(&d->usb_mutex); mutex_init(&d->i2c_mutex); @@ -303,6 +330,7 @@ int dvb_usb_device_init(struct usb_interface *intf, info("found a '%s' in warm state.", desc->name); d->udev =3D udev; d->desc =3D desc; + d->dead =3D 0; d->owner =3D owner; =20 usb_set_intfdata(intf, d); diff --git a/drivers/media/usb/dvb-usb/dvb-usb-urb.c b/drivers/media/usb/dv= b-usb/dvb-usb-urb.c index 2aabf90d8697..6f949b6c3499 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb-urb.c +++ b/drivers/media/usb/dvb-usb/dvb-usb-urb.c @@ -17,13 +17,22 @@ int dvb_usb_generic_rw(struct dvb_usb_device *d, u8 *wb= uf, u16 wlen, u8 *rbuf, if (!d || wbuf =3D=3D NULL || wlen =3D=3D 0) return -EINVAL; =20 + if (!dvb_usb_device_get(d)) + return -ENODEV; + if (d->props.generic_bulk_ctrl_endpoint =3D=3D 0) { err("endpoint for generic control not specified."); - return -EINVAL; + ret =3D -EINVAL; + goto out; + } + + if (d->dead) { + ret =3D -ENODEV; + goto out; } =20 if ((ret =3D mutex_lock_interruptible(&d->usb_mutex))) - return ret; + goto out; =20 deb_xfer(">>> "); debug_dump(wbuf,wlen,deb_xfer); @@ -57,6 +66,8 @@ int dvb_usb_generic_rw(struct dvb_usb_device *d, u8 *wbuf= , u16 wlen, u8 *rbuf, } =20 mutex_unlock(&d->usb_mutex); +out: + dvb_usb_device_put(d); return ret; } EXPORT_SYMBOL(dvb_usb_generic_rw); diff --git a/drivers/media/usb/dvb-usb/dvb-usb.h b/drivers/media/usb/dvb-us= b/dvb-usb.h index 550006a8d86f..311cd3abb60a 100644 --- a/drivers/media/usb/dvb-usb/dvb-usb.h +++ b/drivers/media/usb/dvb-usb/dvb-usb.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include =20 @@ -429,6 +430,12 @@ struct dvb_usb_adapter { /** * struct dvb_usb_device - object of a DVB USB device * @props: copy of the struct dvb_usb_properties this device belongs to. + * @kref: refcount; the object is only freed once every user that can + * outlive USB disconnect (frontend file descriptors, the frontend + * thread, in-flight i2c/usb transfers) has dropped its reference. + * @dead: set once the USB device is gone; USB/i2c workers must check + * this while holding @kref and skip real hardware I/O. Protects + * access to @udev and to the mutexes below. * @desc: pointer to the device's struct dvb_usb_device_description. * @state: initialization and runtime state of the device. * @@ -462,6 +469,9 @@ struct dvb_usb_device { struct dvb_usb_device_properties props; const struct dvb_usb_device_description *desc; =20 + struct kref kref; + int dead; + struct usb_device *udev; =20 #define DVB_USB_STATE_INIT 0x000 @@ -502,6 +512,9 @@ extern int dvb_usb_device_init(struct usb_interface *, short *adapter_nums); extern void dvb_usb_device_exit(struct usb_interface *); =20 +struct dvb_usb_device *dvb_usb_device_get(struct dvb_usb_device *d); +void dvb_usb_device_put(struct dvb_usb_device *d); + /* the generic read/write method for device control */ extern int __must_check dvb_usb_generic_rw(struct dvb_usb_device *, u8 *, u16, u8 *, u16, int); diff --git a/drivers/media/usb/dvb-usb/ttusb2.c b/drivers/media/usb/dvb-usb= /ttusb2.c index acde6149d278..0e63511c0b3e 100644 --- a/drivers/media/usb/dvb-usb/ttusb2.c +++ b/drivers/media/usb/dvb-usb/ttusb2.c @@ -369,10 +369,20 @@ static int ttusb2_i2c_xfer(struct i2c_adapter *adap,s= truct i2c_msg msg[],int num { struct dvb_usb_device *d =3D i2c_get_adapdata(adap); static u8 obuf[60], ibuf[60]; - int i, write_read, read; + int i =3D 0, write_read, read; =20 - if (mutex_lock_interruptible(&d->i2c_mutex) < 0) - return -EAGAIN; + if (!dvb_usb_device_get(d)) + return -ENODEV; + + if (d->dead) { + i =3D -ENODEV; + goto out; + } + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) { + i =3D -EAGAIN; + goto out; + } =20 if (num > 2) warn("more than 2 i2c messages at a time is not handled yet. TODO."); @@ -426,6 +436,8 @@ static int ttusb2_i2c_xfer(struct i2c_adapter *adap,str= uct i2c_msg msg[],int num } =20 mutex_unlock(&d->i2c_mutex); +out: + dvb_usb_device_put(d); return i; } =20 --=20 2.34.1