autofs-5.1.9 - fix submount shutdown race

From: Ian Kent <raven@themaw.net>

In function master_notify_submount() an expire notification is sent to
existing submounts. automount waits for the task to complete then, if
the submount is exiting, waits for the submount to reach a completion
state.

But the submount can go away during these checks resulting in the
autofs mount point structure field of the mount list structure to be
set to NULL which can then lead to a crash.

Signed-off-by: Ian Kent <raven@themaw.net>
---
 CHANGELOG       |    1 +
 daemon/master.c |   23 +++++++++++++----------
 lib/mounts.c    |    2 ++
 3 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 2da197e5e..da3aee59d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -21,6 +21,7 @@
 - Use AUTOFS_ prefixed environment variables in sample/auto.smb.
 - man/autofs.conf.5: fix manpage formatting.
 - fix memory leak in cache_release().
+- fix submount shutdown race.
 
 02/11/2023 autofs-5.1.9
 - fix kernel mount status notification.
diff --git a/daemon/master.c b/daemon/master.c
index f2c11e90c..6bf67552e 100644
--- a/daemon/master.c
+++ b/daemon/master.c
@@ -1222,22 +1222,24 @@ int master_notify_submount(struct autofs_point *ap, const char *path, enum state
 
 	this = mnts_find_submount(path);
 	if (this) {
+		struct autofs_point *found;
+
 		/* We have found a submount to expire */
 		st_mutex_lock();
-
-		if (this->ap->state == ST_SHUTDOWN) {
+		found = this->ap;
+		if (!found || found->state == ST_SHUTDOWN) {
 			this = NULL;
 			st_mutex_unlock();
 			goto done;
 		}
-
-		this->ap->shutdown = ap->shutdown;
-
-		__st_add_task(this->ap, state);
-
+		found->shutdown = ap->shutdown;
+		__st_add_task(found, state);
 		st_mutex_unlock();
 
-		st_wait_task(this->ap, state, 0);
+		/* This is ok because found isn't dereferenced during
+		 * the wait checks.
+		 */
+		st_wait_task(found, state, 0);
 
 		/*
 		 * If our submount gets to state ST_SHUTDOWN_PENDING or
@@ -1249,8 +1251,9 @@ int master_notify_submount(struct autofs_point *ap, const char *path, enum state
 			struct timespec t = { 0, 300000000 };
 			struct timespec r;
 
-			if (sbmnt->ap->state != ST_SHUTDOWN_PENDING &&
-			    sbmnt->ap->state != ST_SHUTDOWN_FORCE) {
+			if (!sbmnt->ap ||
+			   (sbmnt->ap->state != ST_SHUTDOWN_PENDING &&
+			    sbmnt->ap->state != ST_SHUTDOWN_FORCE)) {
 				ret = 0;
 				mnts_put_mount(sbmnt);
 				break;
diff --git a/lib/mounts.c b/lib/mounts.c
index 114c7ef29..b49227ea2 100644
--- a/lib/mounts.c
+++ b/lib/mounts.c
@@ -1136,7 +1136,9 @@ void mnts_remove_submount(const char *mp)
 	this = mnts_lookup(mp);
 	if (this && this->flags & MNTS_AUTOFS) {
 		this->flags &= ~MNTS_AUTOFS;
+		st_mutex_lock();
 		this->ap = NULL;
+		st_mutex_unlock();
 		list_del_init(&this->submount);
 		__mnts_put_mount(this);
 	}
