From nobody Thu Sep 24 13:42:43 2026 Received: from zg8tmtyylji0my4xnjqumte4.icoremail.net (zg8tmtyylji0my4xnjqumte4.icoremail.net [162.243.164.118]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 7045B3BFAEA; Wed, 23 Sep 2026 09:25:32 +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=1790155537; cv=none; b=ClGWNF4YTL1pNhghVtAL3RNHrQSoJl5zQeO/SiSV9EPyWCm2mjqmk7IstiU6Qgw9keB8JdFVZ40jQ7iNFNCyeWzCUxbrlw0oRjGl6sLt7bOYCxvi/awMQbewmWpVSA5/Ug1ZDPToU8hnq9mmbDZm5dsciPdM78tPmV/Wu7UdmSw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790155537; c=relaxed/simple; bh=nwjgiG58dbg8QXgrIujS5kkvDkFVeEV/roefytOgGDM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=auFmIg00FY0NyyuPOHk9F+Lcnp0d8fDip3Fz6FCYS4twHlxBctJBmDpwu23iIpxZf4ro5iQGj6LP1JBfa7Gwts0q0FavStKqaFBIKT+CqlXPjTXfBSS/J8sl+63lUOB4NQS/Aek/6jU0b1UZ0/w9j7W26t9Dg0bSngKsAhPuMSo= 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=ejEQA94w 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="ejEQA94w" 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=xGWTUt2t371Cnu03fnjFQX0nTdO+7X2DfL qBuDqxQGg=; b=ejEQA94wsVR/opxn5iN3o6VzVT1wOOsyizeYwv/B/JLH1EP/SV 1+V3Wk9UcuiRxTF34XQN3ukQ5M4aZUNGuK31ZWnNVaPVDSIiuc6evGiP0tDfWose 8rp2a4tbZhXiJqQj75ivDMwUv1FDFkkxf/H/OhwGCb9GXb/PNpfQUco80= Received: from localhost.localdomain (unknown [113.200.174.5]) by hzbj-edu-front-3.icoremail.net (Coremail) with SMTP id BbQMCkA2FjUAm7NqdssuAA--.1462S3; Wed, 23 Sep 2026 17:25:27 +0800 (CST) From: Yuanzhe Liu <25031212351@stu.xidian.edu.cn> To: mchehab@kernel.org Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, patrick.boettcher@posteo.de, Yuanzhe Liu <25031212351@stu.xidian.edu.cn>, stable@vger.kernel.org Subject: [PATCH 1/3] media: dvb-core: fix use-after-free in dvb_remove_device() and dvb_device_open() Date: Wed, 23 Sep 2026 17:24:51 +0800 Message-ID: <20260923092454.969-2-25031212351@stu.xidian.edu.cn> X-Mailer: git-send-email 2.45.1.windows.1 In-Reply-To: <20260923092454.969-1-25031212351@stu.xidian.edu.cn> References: <20260923092454.969-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: BbQMCkA2FjUAm7NqdssuAA--.1462S3 X-Coremail-Antispam: 1UD129KBjvJXoW3AF18tr1DWrW5CF17CFyUZFb_yoW7Cw1fpF ZIga45KrWDKr40gr4xAF109F9xArnay3yruFWagr97Kr4fJry5tryvyFWjyw17JrZ7JFyj qrW3Gryku3y5Jr7anT9S1TB71UUUUUJqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUQm14x267AKxVW8JVW5JwAFc2x0x2IEx4CE42xK8VAvwI8IcIk0 rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jr4l82xGYIkIc2 x26xkF7I0E14v26r1Y6r1xM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48ve4kI8wA2z4x0 Y4vE2Ix0cI8IcVAFwI0_JFI_Gr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1l84 ACjcxK6I8E87Iv67AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVCY1x0267AKxVW8Jr0_Cr1U M2kKe7AKxVWUXVWUAwAac4AC62xK8xCEY4vEwIxC4wAS0I0E0xvYzxvE52x082IY62kv04 87Mc02F40EFcxC0VAKzVAqx4xG6I80ewAv7VC0I7IYx2IY67AKxVWUJVWUGwAv7VC2z280 aVAFwI0_Jr0_Gr1lOx8S6xCaFVCjc4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JM4x0x7Aq67 IIx4CEVc8vx2IErcIFxwCY1x0262kKe7AKxVWUAVWUtwCY02Avz4vE14v_GFWl42xK82IY c2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAqx4xG67AKxVWUJVWUGwC20s 026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r126r1DMIIYrxkI7VAKI48JMIIF 0xvE2Ix0cI8IcVAFwI0_Jr0_JF4lIxAIcVC0I7IYx2IY6xkF7I0E14v26r4j6F4UMIIF0x vE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67AKxVWUJVW8JwCI42IY6I8E87Iv 6xkF7I0E14v26r4j6r4UJbIYCTnIWIevJa73UjIFyTuYvjfU1LvKDUUUU X-CM-SenderInfo: ysvqjiysrsjkur6v33wo0lvxldqovvfxof0/1tbiAQUFD2qyndayuwABsV 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(). Cc: stable@vger.kernel.org Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") 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 d753d32..9eb8862 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.45.1.windows.1 From nobody Thu Sep 24 13:42:43 2026 Received: from zg8tmtyylji0my4xnjqumte4.icoremail.net (zg8tmtyylji0my4xnjqumte4.icoremail.net [162.243.164.118]) by smtp.subspace.kernel.org (Postfix) with ESMTP id C25203E5EF8; Wed, 23 Sep 2026 09:25: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=1790155544; cv=none; b=c4eXODqtaIShfM+mkj68UBj5DOxyKrInbcJ9ImZjPInAuUPc0roQP1O4To+Xo7GK2Y8EcnZ3gjm3vuhy+kpv3kMlWC+8EEbp4Zk/XOTWNYV5hYEXgqjm/EC/F3SlLA/SBhpWM3gd3uKrAgMMUg0KwozHXfVbsO9nuqA1WaSKWM8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790155544; c=relaxed/simple; bh=jB8z6sEMowFvzLsPkiRETp3H3c5r4Nx1H2rLcot9d8A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=BixMJ0j4HtKmjiJo+zd1w59uV72STUE691huULAyfK1l3gjf49EOJ7Ynji1CAqwnw7NcOgRUjVBU6YoB6urYF6oX+kW5ZnmZ+KJu1K48srY2hxFp60MpQOs4O+hPc9gqqVAGfwHplzGeSV1buRjsPSSul2AtnEybt8YV8OHHwIY= 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=r/4/TNQO 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="r/4/TNQO" 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-Type: Content-Transfer-Encoding; bh=RybKETcbixVV3yVyAXW3M5o74IbsAhytgE aToaZycY8=; b=r/4/TNQOnacRKF6ivi9JVlEAoIx7f58CWq/oOx/3dMtWgmH03n bqHsrPRIBOPcfvfLs0Q+7mgE5wAUybWIdeTEOTRVVwcK6iSyoS8b/8uzckaP2seo F7tFSiiKEQoo46cvGhBErCI4HeyqGdKe4G09X9Lrfaj4MuTFngVlfdLaw= Received: from localhost.localdomain (unknown [113.200.174.5]) by hzbj-edu-front-3.icoremail.net (Coremail) with SMTP id BbQMCkA2FjUAm7NqdssuAA--.1462S4; Wed, 23 Sep 2026 17:25:32 +0800 (CST) From: Yuanzhe Liu <25031212351@stu.xidian.edu.cn> To: mchehab@kernel.org Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, patrick.boettcher@posteo.de, Yuanzhe Liu <25031212351@stu.xidian.edu.cn>, stable@vger.kernel.org Subject: [PATCH 2/3] media: dvb-core: keep dvb_device alive while checking users count in frontend release paths Date: Wed, 23 Sep 2026 17:24:52 +0800 Message-ID: <20260923092454.969-3-25031212351@stu.xidian.edu.cn> X-Mailer: git-send-email 2.45.1.windows.1 In-Reply-To: <20260923092454.969-1-25031212351@stu.xidian.edu.cn> References: <20260923092454.969-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-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-CM-TRANSID: BbQMCkA2FjUAm7NqdssuAA--.1462S4 X-Coremail-Antispam: 1UD129KBjvJXoWxXw4fAr1DuFy3JFy8WF45KFg_yoW5GrW8pF Z3JFW5KF4UKwn7Wrs2y3WDur9YvF1Syry5Ga4xtanakr1fJ348KryYga4v9rsxGrs3JrWj qwsrGaykC3y7ZaDanT9S1TB71UUUUUDqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUmC14x267AKxVW5JVWrJwAFc2x0x2IEx4CE42xK8VAvwI8IcIk0 rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jryl82xGYIkIc2 x26xkF7I0E14v26r4j6ryUM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48ve4kI8wA2z4x0 Y4vE2Ix0cI8IcVAFwI0_JFI_Gr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1l84 ACjcxK6I8E87Iv67AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVCY1x0267AKxVW8Jr0_Cr1U M2vYz4IE04k24VAvwVAKI4IrM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64 kE6c02F40Ex7xfMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm 72CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lF7I21c0EjII2zVCS5cI20VAGYx C7MxkF7I0En4kS14v26r126r1DMxkIecxEwVAFwVW8uwCF04k20xvY0x0EwIxGrwCFx2Iq xVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r 106r1rMI8E67AF67kF1VAFwI0_JF0_Jw1lIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AK xVWUJVWUCwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1lIxAIcVCF04k26cxKx2IYs7 xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r1j6r4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr0_ Gr1UYxBIdaVFxhVjvjDU0xZFpf9x0pRt73kUUUUU= X-CM-SenderInfo: ysvqjiysrsjkur6v33wo0lvxldqovvfxof0/1tbiAQUFD2qyndaywgAAst 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 =E2=80=94 e.g. USB disconnect has already removed the minors entry and this was the last open() fd =E2=80=94 "dvbdev" is freed before ->users is read: dvb_frontend_release+0x28b/0x2b0 at dvb_frontend.c:2903/2916 BUG: KASAN: slab-use-after-free Read of size 8 (the same pattern exists at err1: after dvb_generic_release() in dvb_frontend_open()) Take an extra kref around the post-release checks so the object stays alive until we're done with it. While there, also grab it in dvb_frontend_open() before the same late ->users check on the error path; the reference itself is dropped right after, which is safe because fops->release() no longer runs on an erroring open(). Cc: stable@vger.kernel.org Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Yuanzhe Liu <25031212351@stu.xidian.edu.cn> --- drivers/media/dvb-core/dvb_frontend.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/media/dvb-core/dvb_frontend.c b/drivers/media/dvb-core= /dvb_frontend.c index d082b6c..9f92af3 100644 --- a/drivers/media/dvb-core/dvb_frontend.c +++ b/drivers/media/dvb-core/dvb_frontend.c @@ -2888,9 +2888,18 @@ static int dvb_frontend_open(struct inode *inode, st= ruct file *file) err2: #endif dvb_generic_release(inode, file); + + /* + * dvb_generic_release() may have dropped the last kref, so the + * object can already be gone. Take an extra reference before + * poking at ->users. + */ err1: + if (!dvb_device_get(dvbdev)) + return ret; if (dvbdev->users =3D=3D -1 && fe->ops.ts_bus_ctrl) fe->ops.ts_bus_ctrl(fe, 0); + dvb_device_put(dvbdev); err0: if (adapter->mfe_shared) mutex_unlock(&adapter->mfe_lock); @@ -2913,6 +2922,13 @@ static int dvb_frontend_release(struct inode *inode,= struct file *file) =20 ret =3D dvb_generic_release(inode, file); =20 + /* + * dvb_generic_release() ends with dvb_device_put(); make sure the + * object survives until after the ->users check below. + */ + if (!dvb_device_get(dvbdev)) + return ret; + if (dvbdev->users =3D=3D -1) { wake_up(&fepriv->wait_queue); #ifdef CONFIG_MEDIA_CONTROLLER_DVB @@ -2931,6 +2947,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.45.1.windows.1 From nobody Thu Sep 24 13:42:43 2026 Received: from zg8tmtyylji0my4xnjqumte4.icoremail.net (zg8tmtyylji0my4xnjqumte4.icoremail.net [162.243.164.118]) by smtp.subspace.kernel.org (Postfix) with ESMTP id C25D044236F; Wed, 23 Sep 2026 09:25: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=1790155544; cv=none; b=C7wA359IRCIN4NQ5Bd/qUd16W+YomYMLHlkJ81j3TemzL6vemXujpT7fvL0TXnivJy1Qg69N43Mm4YvyrMvBMlOY7zVnXXUnOrjFIKkLoF+Q+9AZqtpdwyC2VF7hAOHTm3fus8ngL3gVOmwwuC2gIMQokzsde+WNHlo2eDZMJhw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790155544; c=relaxed/simple; bh=V1D5+Z9rTGG/RBL8PRT2z6MWE+RfONvHEJVad0W+wHc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jVPrKhBwev7FbpI74Lx51Morg/FrqwUknFQqcvbp+L4VDXXl+YoybF7ClcwImgn6DfTym2boyoucgI+Ea8Kf97BxmzlJT+tB3A5mQcRvuN9NcJwDwM0tk+NG0hgoJGkxpT28Kf9gOfv3m4bCinNXvzB9v4iW7Xmu3TXXFsbYVFo= 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=F6fK6m8V 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="F6fK6m8V" 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=TJoPsQSc9gwyJ/4QwQW3C2TmqpUzn6o245 JE3ohPle0=; b=F6fK6m8V75iLl9JTPpMOuNkQWF/3YNMHOI8+JJzTU5wVAk1WHE APVCWGunvczEtHjq/+EswhW5AIa18LoBUQVNuAA9vVmoZRTOHLslP4a8EVePA/AA a+1zKInQqFZBEBjzWExBLF2w2rnX2xKp25vCg4C3Vi0k9H5/pWb6p9vpY= Received: from localhost.localdomain (unknown [113.200.174.5]) by hzbj-edu-front-3.icoremail.net (Coremail) with SMTP id BbQMCkA2FjUAm7NqdssuAA--.1462S5; Wed, 23 Sep 2026 17:25:36 +0800 (CST) From: Yuanzhe Liu <25031212351@stu.xidian.edu.cn> To: mchehab@kernel.org Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, patrick.boettcher@posteo.de, Yuanzhe Liu <25031212351@stu.xidian.edu.cn>, stable@vger.kernel.org Subject: [PATCH 3/3] media: dvb-usb: refcount dvb_usb_device to fix disconnect UAF on open chardevs Date: Wed, 23 Sep 2026 17:24:53 +0800 Message-ID: <20260923092454.969-4-25031212351@stu.xidian.edu.cn> X-Mailer: git-send-email 2.45.1.windows.1 In-Reply-To: <20260923092454.969-1-25031212351@stu.xidian.edu.cn> References: <20260923092454.969-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: BbQMCkA2FjUAm7NqdssuAA--.1462S5 X-Coremail-Antispam: 1UD129KBjvJXoWxKF4xKw13Xw4rXw4DAr13Arb_yoWDGw13pa 1DKFWrKrWUGrn7Ww4UArn8Xrs5Ga1vka4rKryxGw1agFs3C34UGry8Kry5tw4rG397JFy2 q3WUWryUKF1UGr7anT9S1TB71UUUUUDqnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUmC14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK8VAvwI8IcIk0 rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JrWl82xGYIkIc2 x26xkF7I0E14v26r4j6ryUM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48ve4kI8wA2z4x0 Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1l84 ACjcxK6I8E87Iv67AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVCY1x0267AKxVW8Jr0_Cr1U M2vYz4IE04k24VAvwVAKI4IrM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64 kE6c02F40Ex7xfMcIj6xIIjxv20xvE14v26r1j6r18McIj6I8E87Iv67AKxVWUJVW8JwAm 72CE4IkC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lF7I21c0EjII2zVCS5cI20VAGYx C7MxkF7I0En4kS14v26r126r1DMxkIecxEwVAFwVW8uwCF04k20xvY0x0EwIxGrwCFx2Iq xVCFs4IE7xkEbVWUJVW8JwC20s026c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r 106r1rMI8E67AF67kF1VAFwI0_JF0_Jw1lIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AK xVWUJVWUCwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1lIxAIcVCF04k26cxKx2IYs7 xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r1j6r4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr0_ Gr1UYxBIdaVFxhVjvjDU0xZFpf9x0pREzuAUUUUU= X-CM-SenderInfo: ysvqjiysrsjkur6v33wo0lvxldqovvfxof0/1tbiAQUFD2qyndaywgABss 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. Cc: stable@vger.kernel.org Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") 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 029dad8..e9c15a4 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 6d2672b..3b68d91 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 2aabf90..6f949b6 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 550006a..311cd3a 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 acde614..0e63511 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.45.1.windows.1