diff mbox series

ALSA: info: Fix potential deadlock at disconnection

Message ID 20231109141954.4283-1-tiwai@suse.de (mailing list archive)
State Accepted
Commit c7a60651953359f98dbf24b43e1bf561e1573ed4
Headers show
Series ALSA: info: Fix potential deadlock at disconnection | expand

Commit Message

Takashi Iwai Nov. 9, 2023, 2:19 p.m. UTC
As reported recently, ALSA core info helper may cause a deadlock at
the forced device disconnection during the procfs operation.

The proc_remove() (that is called from the snd_card_disconnect()
helper) has a synchronization of the pending procfs accesses via
wait_for_completion().  Meanwhile, ALSA procfs helper takes the global
mutex_lock(&info_mutex) at both the proc_open callback and
snd_card_info_disconnect() helper.  Since the proc_open can't finish
due to the mutex lock, wait_for_completion() never returns, either,
hence it deadlocks.

	TASK#1				TASK#2
	proc_reg_open()
	  takes use_pde()
	snd_info_text_entry_open()
					snd_card_disconnect()
					snd_info_card_disconnect()
					  takes mutex_lock(&info_mutex)
					proc_remove()
					wait_for_completion(unused_pde)
					  ... waiting task#1 closes
	mutex_lock(&info_mutex)
		=> DEADLOCK

This patch is a workaround for avoiding the deadlock scenario above.

The basic strategy is to move proc_remove() call outside the mutex
lock.  proc_remove() can work gracefully without extra locking, and it
can delete the tree recursively alone.  So, we call proc_remove() at
snd_info_card_disconnection() at first, then delete the rest resources
recursively within the info_mutex lock.

After the change, the function snd_info_disconnect() doesn't do
disconnection by itself any longer, but it merely clears the procfs
pointer.  So rename the function to snd_info_clear_entries() for
avoiding confusion.

The similar change is applied to snd_info_free_entry(), too.  Since
the proc_remove() is called only conditionally with the non-NULL
entry->p, it's skipped after the snd_info_clear_entries() call.

Reported-by: Shinhyung Kang <s47.kang@samsung.com>
Closes: https://lore.kernel.org/r/664457955.21699345385931.JavaMail.epsvc@epcpadp4
Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 sound/core/info.c | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

Comments

Jaroslav Kysela Nov. 9, 2023, 3:04 p.m. UTC | #1
On 09. 11. 23 15:19, Takashi Iwai wrote:
> As reported recently, ALSA core info helper may cause a deadlock at
> the forced device disconnection during the procfs operation.
> 
> The proc_remove() (that is called from the snd_card_disconnect()
> helper) has a synchronization of the pending procfs accesses via
> wait_for_completion().  Meanwhile, ALSA procfs helper takes the global
> mutex_lock(&info_mutex) at both the proc_open callback and
> snd_card_info_disconnect() helper.  Since the proc_open can't finish
> due to the mutex lock, wait_for_completion() never returns, either,
> hence it deadlocks.

Really nice fix. Thanks Takashi.

Reviewed-by: Jaroslav Kysela <perex@perex.cz>

(maybe Cc: to stable?)

					Jaroslav
Takashi Iwai Nov. 9, 2023, 3:07 p.m. UTC | #2
On Thu, 09 Nov 2023 16:04:21 +0100,
Jaroslav Kysela wrote:
> 
> On 09. 11. 23 15:19, Takashi Iwai wrote:
> > As reported recently, ALSA core info helper may cause a deadlock at
> > the forced device disconnection during the procfs operation.
> > 
> > The proc_remove() (that is called from the snd_card_disconnect()
> > helper) has a synchronization of the pending procfs accesses via
> > wait_for_completion().  Meanwhile, ALSA procfs helper takes the global
> > mutex_lock(&info_mutex) at both the proc_open callback and
> > snd_card_info_disconnect() helper.  Since the proc_open can't finish
> > due to the mutex lock, wait_for_completion() never returns, either,
> > hence it deadlocks.
> 
> Really nice fix. Thanks Takashi.
> 
> Reviewed-by: Jaroslav Kysela <perex@perex.cz>
> 
> (maybe Cc: to stable?)

Yes, I'll add it.


thanks,

Takashi
강신형 Nov. 10, 2023, 2:44 a.m. UTC | #3
> As reported recently, ALSA core info helper may cause a deadlock at the
forced device disconnection during the procfs operation.
>
> The proc_remove() (that is called from the snd_card_disconnect()
> helper) has a synchronization of the pending procfs accesses via
wait_for_completion().  Meanwhile, ALSA procfs helper takes the global
> mutex_lock(&info_mutex) at both the proc_open callback and
> snd_card_info_disconnect() helper.  Since the proc_open can't finish due
to the mutex lock, wait_for_completion() never returns, either, hence it
deadlocks.

As you said, the issue is easily reproduced after adding extra delay.
And the problem is solved completely with your patch.
Thanks for your help!


Thanks.
Shinhyung Kang.
diff mbox series

Patch

diff --git a/sound/core/info.c b/sound/core/info.c
index 0b2f04dcb589..e2f302e55bbb 100644
--- a/sound/core/info.c
+++ b/sound/core/info.c
@@ -56,7 +56,7 @@  struct snd_info_private_data {
 };
 
 static int snd_info_version_init(void);
-static void snd_info_disconnect(struct snd_info_entry *entry);
+static void snd_info_clear_entries(struct snd_info_entry *entry);
 
 /*
 
@@ -569,11 +569,16 @@  void snd_info_card_disconnect(struct snd_card *card)
 {
 	if (!card)
 		return;
-	mutex_lock(&info_mutex);
+
 	proc_remove(card->proc_root_link);
-	card->proc_root_link = NULL;
 	if (card->proc_root)
-		snd_info_disconnect(card->proc_root);
+		proc_remove(card->proc_root->p);
+
+	mutex_lock(&info_mutex);
+	if (card->proc_root)
+		snd_info_clear_entries(card->proc_root);
+	card->proc_root_link = NULL;
+	card->proc_root = NULL;
 	mutex_unlock(&info_mutex);
 }
 
@@ -745,15 +750,14 @@  struct snd_info_entry *snd_info_create_card_entry(struct snd_card *card,
 }
 EXPORT_SYMBOL(snd_info_create_card_entry);
 
-static void snd_info_disconnect(struct snd_info_entry *entry)
+static void snd_info_clear_entries(struct snd_info_entry *entry)
 {
 	struct snd_info_entry *p;
 
 	if (!entry->p)
 		return;
 	list_for_each_entry(p, &entry->children, list)
-		snd_info_disconnect(p);
-	proc_remove(entry->p);
+		snd_info_clear_entries(p);
 	entry->p = NULL;
 }
 
@@ -770,8 +774,9 @@  void snd_info_free_entry(struct snd_info_entry * entry)
 	if (!entry)
 		return;
 	if (entry->p) {
+		proc_remove(entry->p);
 		mutex_lock(&info_mutex);
-		snd_info_disconnect(entry);
+		snd_info_clear_entries(entry);
 		mutex_unlock(&info_mutex);
 	}