autofs-5.1.9 - fix map source deadlock for recursive submounts

From: Ian Kent <raven@themaw.net>

Commit 4e2438c18ed3 ("autofs-5.1.2 - add ref counting to struct
map_source") introduced a reference count to the map source struct in
order to allow multiple threads to use the same map source. In doing
this it used the map source read-write lock write lock to protect the
reference count field. Unfortunately this change has the side effect of
introducing a deadlock when using recursive submounts.

Fix this by using the map source read lock to find the mount and add
a mutex to protect the reference count itself.

Signed-off-by: Ian Kent <raven@themaw.net>
---
 CHANGELOG        |    1 +
 daemon/master.c  |   39 +++++++++++++++++++++++++++++++++++++--
 include/master.h |    3 +++
 3 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index ef095c7e0..90de4a74f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -106,6 +106,7 @@
 - add cifs mount module.
 - refactor mount_nfs.c:mount_mount().
 - fix a couple of compile warnings.
+- fix map source deadlock for recursive submounts.
 
 02/11/2023 autofs-5.1.9
 - fix kernel mount status notification.
diff --git a/daemon/master.c b/daemon/master.c
index 8b7450e73..a13912e61 100644
--- a/daemon/master.c
+++ b/daemon/master.c
@@ -284,6 +284,10 @@ master_add_map_source(struct master_mapent *entry,
 
 	master_source_unlock(entry);
 
+	status = pthread_mutex_init(&source->ref_mutex, NULL);
+	if (status)
+		fatal(status);
+
 	status = pthread_rwlock_init(&source->module_lock, NULL);
 	if (status)
 		fatal(status);
@@ -366,10 +370,13 @@ master_get_map_source(struct master_mapent *entry,
 {
 	struct map_source *source = NULL;
 
-	master_source_writelock(entry);
+	master_source_readlock(entry);
 	source = __master_find_map_source(entry, type, format, argc, argv);
-	if (source)
+	if (source) {
+		source_ref_lock(source);
 		source->ref++;
+		source_ref_unlock(source);
+	}
 	master_source_unlock(entry);
 
 	return source;
@@ -418,6 +425,10 @@ static void __master_free_map_source(struct map_source *source, unsigned int fre
 		source->instance = NULL;
 	}
 
+	status = pthread_mutex_destroy(&source->ref_mutex);
+	if (status)
+		fatal(status);
+
 	status = pthread_rwlock_destroy(&source->module_lock);
 	if (status)
 		fatal(status);
@@ -629,6 +640,30 @@ void send_map_update_request(struct autofs_point *ap)
 	return;
 }
 
+void source_ref_lock(struct map_source *source)
+{
+	int status;
+
+	status = pthread_mutex_lock(&source->ref_mutex);
+	if (status) {
+		logmsg("map source write lock failed");
+		fatal(status);
+	}
+	return;
+}
+
+void source_ref_unlock(struct map_source *source)
+{
+	int status;
+
+	status = pthread_mutex_unlock(&source->ref_mutex);
+	if (status) {
+		logmsg("map source unlock failed");
+		fatal(status);
+	}
+	return;
+}
+
 void master_source_writelock(struct master_mapent *entry)
 {
 	int status;
diff --git a/include/master.h b/include/master.h
index 3ca7ad8d6..d1e372cfa 100644
--- a/include/master.h
+++ b/include/master.h
@@ -24,6 +24,7 @@
 
 struct map_source {
 	unsigned int ref;
+	pthread_mutex_t ref_mutex;
 	unsigned int flags;
 	char *type;
 	char *format;
@@ -90,6 +91,8 @@ struct map_source *
 master_add_map_source(struct master_mapent *, char *, char *, time_t, int, const char **);
 struct map_source *
 master_find_map_source(struct master_mapent *, const char *, const char *, int, const char **);
+void source_ref_lock(struct map_source *);
+void source_ref_unlock(struct map_source *);
 struct map_source *
 master_get_map_source(struct master_mapent *, const char *, const char *, int, const char **);
 void master_free_map_source(struct map_source *, unsigned int);
