git: 67864268da53 - main - devfs: add integrity asserts for cdevp_list

From: Jason A. Harmening <jah_at_FreeBSD.org>
Date: Thu, 21 Sep 2023 16:51:24 UTC
The branch main has been updated by jah:

URL: https://cgit.FreeBSD.org/src/commit/?id=67864268da53b792836f13be10299de8cd62997e

commit 67864268da53b792836f13be10299de8cd62997e
Author:     Jason A. Harmening <jah@FreeBSD.org>
AuthorDate: 2023-09-19 13:44:34 +0000
Commit:     Jason A. Harmening <jah@FreeBSD.org>
CommitDate: 2023-09-21 16:51:12 +0000

    devfs: add integrity asserts for cdevp_list
    
    It's possible for misuse of cdev KPIs or for bugs in devfs itself to
    result in e.g. a cdev object's container being freed while still on the
    global list used to populate each devfs mount; see PR 273418 for a
    recent example.
    
    Since a node may be marked inactive well before it is reaped from the
    list, add a new flag solely to track list membership, and employ it in
    some basic list integrity assertions to catch bad actors.
    
    Discussed with: kib, mjg
    MFC after:      1 week
---
 sys/fs/devfs/devfs_devs.c  | 12 +++++++++++-
 sys/fs/devfs/devfs_int.h   |  1 +
 sys/fs/devfs/devfs_vnops.c |  4 ++++
 sys/kern/kern_conf.c       |  2 ++
 4 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/sys/fs/devfs/devfs_devs.c b/sys/fs/devfs/devfs_devs.c
index c6efd0d421b1..db879efe803a 100644
--- a/sys/fs/devfs/devfs_devs.c
+++ b/sys/fs/devfs/devfs_devs.c
@@ -175,6 +175,9 @@ devfs_free(struct cdev *cdev)
 	struct cdev_priv *cdp;
 
 	cdp = cdev2priv(cdev);
+	KASSERT((cdp->cdp_flags & (CDP_ACTIVE | CDP_ON_ACTIVE_LIST)) == 0,
+	    ("%s: cdp %p (%s) still on active list",
+	    __func__, cdp, cdev->si_name));
 	if (cdev->si_cred != NULL)
 		crfree(cdev->si_cred);
 	devfs_free_cdp_inode(cdp->cdp_inode);
@@ -516,6 +519,9 @@ devfs_populate_loop(struct devfs_mount *dm, int cleanup)
 	dev_lock();
 	TAILQ_FOREACH(cdp, &cdevp_list, cdp_list) {
 		KASSERT(cdp->cdp_dirents != NULL, ("NULL cdp_dirents"));
+		KASSERT((cdp->cdp_flags & CDP_ON_ACTIVE_LIST) != 0,
+		    ("%s: cdp %p (%s) should not be on active list",
+		    __func__, cdp, cdp->cdp_c.si_name));
 
 		/*
 		 * If we are unmounting, or the device has been destroyed,
@@ -547,6 +553,7 @@ devfs_populate_loop(struct devfs_mount *dm, int cleanup)
 		if (!(cdp->cdp_flags & CDP_ACTIVE)) {
 			if (cdp->cdp_inuse > 0)
 				continue;
+			cdp->cdp_flags &= ~CDP_ON_ACTIVE_LIST;
 			TAILQ_REMOVE(&cdevp_list, cdp, cdp_list);
 			dev_unlock();
 			dev_rel(&cdp->cdp_c);
@@ -698,7 +705,10 @@ devfs_create(struct cdev *dev)
 
 	dev_lock_assert_locked();
 	cdp = cdev2priv(dev);
-	cdp->cdp_flags |= CDP_ACTIVE;
+	KASSERT((cdp->cdp_flags & CDP_ON_ACTIVE_LIST) == 0,
+	    ("%s: cdp %p (%s) already on active list",
+	    __func__, cdp, dev->si_name));
+	cdp->cdp_flags |= (CDP_ACTIVE | CDP_ON_ACTIVE_LIST);
 	cdp->cdp_inode = alloc_unrl(devfs_inos);
 	dev_refl(dev);
 	TAILQ_INSERT_TAIL(&cdevp_list, cdp, cdp_list);
diff --git a/sys/fs/devfs/devfs_int.h b/sys/fs/devfs/devfs_int.h
index 32c6fb414250..916297425b53 100644
--- a/sys/fs/devfs/devfs_int.h
+++ b/sys/fs/devfs/devfs_int.h
@@ -55,6 +55,7 @@ struct cdev_priv {
 #define CDP_ACTIVE		(1 << 0)
 #define CDP_SCHED_DTR		(1 << 1)
 #define	CDP_UNREF_DTR		(1 << 2)
+#define CDP_ON_ACTIVE_LIST	(1 << 3)
 
 	u_int			cdp_inuse;
 	u_int			cdp_maxdirent;
diff --git a/sys/fs/devfs/devfs_vnops.c b/sys/fs/devfs/devfs_vnops.c
index 2f700f9dad25..1df7d13be919 100644
--- a/sys/fs/devfs/devfs_vnops.c
+++ b/sys/fs/devfs/devfs_vnops.c
@@ -1664,6 +1664,10 @@ devfs_revoke(struct vop_revoke_args *ap)
 	dev_lock();
 	cdp->cdp_inuse--;
 	if (!(cdp->cdp_flags & CDP_ACTIVE) && cdp->cdp_inuse == 0) {
+		KASSERT((cdp->cdp_flags & CDP_ON_ACTIVE_LIST) != 0,
+		    ("%s: cdp %p (%s) not on active list",
+		    __func__, cdp, dev->si_name));
+		cdp->cdp_flags &= ~CDP_ON_ACTIVE_LIST;
 		TAILQ_REMOVE(&cdevp_list, cdp, cdp_list);
 		dev_unlock();
 		dev_rel(&cdp->cdp_c);
diff --git a/sys/kern/kern_conf.c b/sys/kern/kern_conf.c
index d6063696c85b..a7c22b7d118a 100644
--- a/sys/kern/kern_conf.c
+++ b/sys/kern/kern_conf.c
@@ -119,6 +119,8 @@ dev_free_devlocked(struct cdev *cdev)
 	cdp = cdev2priv(cdev);
 	KASSERT((cdp->cdp_flags & CDP_UNREF_DTR) == 0,
 	    ("destroy_dev() was not called after delist_dev(%p)", cdev));
+	KASSERT((cdp->cdp_flags & CDP_ON_ACTIVE_LIST) == 0,
+	    ("%s: cdp %p (%s) on active list", __func__, cdp, cdev->si_name));
 	TAILQ_INSERT_HEAD(&cdevp_free_list, cdp, cdp_list);
 }