[PATCH 0/4] ALSA: Defer async signal handling
Hi,
this series is another attempt to address the potential deadlocks via kill_fasync() calls that have been reported by syzbot for long time. Instead of the previous series to drop the async handler[*], this tries to defer the kill_fasync() call. A few new common helpers are introduced at first, then the actual usages are replaced in the following patches.
The patches passed the quick tests with alsa-lib test cases.
Takashi
[*] https://lore.kernel.org/r/20220717070549.5993-1-tiwai@suse.de
===
Takashi Iwai (4): ALSA: core: Add async signal helpers ALSA: timer: Use deferred fasync helper ALSA: pcm: Use deferred fasync helper ALSA: control: Use deferred fasync helper
include/sound/control.h | 2 +- include/sound/core.h | 8 ++++ include/sound/pcm.h | 2 +- sound/core/control.c | 7 ++-- sound/core/misc.c | 93 +++++++++++++++++++++++++++++++++++++++++ sound/core/pcm.c | 1 + sound/core/pcm_lib.c | 2 +- sound/core/pcm_native.c | 2 +- sound/core/timer.c | 11 ++--- 9 files changed, 116 insertions(+), 12 deletions(-)
Currently the call of kill_fasync() from an interrupt handler might lead to potential spin deadlocks, as spotted by syzkaller. Unfortunately, it's not so trivial to fix this lock chain as it's involved with the tasklist_lock that is touched in allover places.
As a temporary workaround, this patch provides the way to defer the async signal notification in a work. The new helper functions, snd_fasync_helper() and snd_kill_faync() are replacements for fasync_helper() and kill_fasync(), respectively. In addition, snd_fasync_free() needs to be called at the destructor of the relevant file object.
Signed-off-by: Takashi Iwai tiwai@suse.de --- include/sound/core.h | 8 ++++ sound/core/misc.c | 93 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+)
diff --git a/include/sound/core.h b/include/sound/core.h index dd28de2343b8..4365c35d038b 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -507,4 +507,12 @@ snd_pci_quirk_lookup_id(u16 vendor, u16 device, } #endif
+/* async signal helpers */ +struct snd_fasync; + +int snd_fasync_helper(int fd, struct file *file, int on, + struct snd_fasync **fasyncp); +void snd_kill_fasync(struct snd_fasync *fasync, int signal, int poll); +void snd_fasync_free(struct snd_fasync *fasync); + #endif /* __SOUND_CORE_H */ diff --git a/sound/core/misc.c b/sound/core/misc.c index 50e4aaa6270d..3e50e1d5d09f 100644 --- a/sound/core/misc.c +++ b/sound/core/misc.c @@ -145,3 +145,96 @@ snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list) } EXPORT_SYMBOL(snd_pci_quirk_lookup); #endif + +/* + * Deferred async signal helpers + * + * Below are a few helper functions to wrap the async signal handling + * in the deferred work. The main purpose is to avoid the messy deadlock + * around tasklist_lock and co at the kill_fasync() invocation. + * fasync_helper() and kill_fasync() are replaced with snd_fasync_helper() + * and snd_kill_fasync(), respectively. In addition, snd_fasync_free() has + * to be called at releasing the relevant file object. + */ +struct snd_fasync { + struct fasync_struct *fasync; + int signal; + int poll; + int on; + struct list_head list; +}; + +static DEFINE_SPINLOCK(snd_fasync_lock); +static LIST_HEAD(snd_fasync_list); + +static void snd_fasync_work_fn(struct work_struct *work) +{ + struct snd_fasync *fasync; + + spin_lock_irq(&snd_fasync_lock); + while (!list_empty(&snd_fasync_list)) { + fasync = list_first_entry(&snd_fasync_list, struct snd_fasync, list); + list_del_init(&fasync->list); + spin_unlock_irq(&snd_fasync_lock); + if (fasync->on) + kill_fasync(&fasync->fasync, fasync->signal, fasync->poll); + spin_lock_irq(&snd_fasync_lock); + } + spin_unlock_irq(&snd_fasync_lock); +} + +static DECLARE_WORK(snd_fasync_work, snd_fasync_work_fn); + +int snd_fasync_helper(int fd, struct file *file, int on, + struct snd_fasync **fasyncp) +{ + struct snd_fasync *fasync = NULL; + + if (on) { + fasync = kzalloc(sizeof(*fasync), GFP_KERNEL); + if (!fasync) + return -ENOMEM; + INIT_LIST_HEAD(&fasync->list); + } + + spin_lock_irq(&snd_fasync_lock); + if (*fasyncp) { + kfree(fasync); + fasync = *fasyncp; + } else { + if (!fasync) { + spin_unlock_irq(&snd_fasync_lock); + return 0; + } + *fasyncp = fasync; + } + fasync->on = on; + spin_unlock_irq(&snd_fasync_lock); + return fasync_helper(fd, file, on, &fasync->fasync); +} +EXPORT_SYMBOL_GPL(snd_fasync_helper); + +void snd_kill_fasync(struct snd_fasync *fasync, int signal, int poll) +{ + unsigned long flags; + + if (!fasync || !fasync->on) + return; + spin_lock_irqsave(&snd_fasync_lock, flags); + fasync->signal = signal; + fasync->poll = poll; + list_move(&fasync->list, &snd_fasync_list); + schedule_work(&snd_fasync_work); + spin_unlock_irqrestore(&snd_fasync_lock, flags); +} +EXPORT_SYMBOL_GPL(snd_kill_fasync); + +void snd_fasync_free(struct snd_fasync *fasync) +{ + if (!fasync) + return; + fasync->on = 0; + flush_work(&snd_fasync_work); + kfree(fasync); +} +EXPORT_SYMBOL_GPL(snd_fasync_free);
Hi Takashi,
I love your patch! Yet something to improve:
[auto build test ERROR on tiwai-sound/for-next] [also build test ERROR on linus/master v5.19-rc8 next-20220726] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Takashi-Iwai/ALSA-Defer-async... base: https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git for-next config: csky-randconfig-r014-20220726 (https://download.01.org/0day-ci/archive/20220727/202207270950.zWfcvyEK-lkp@i...) compiler: csky-linux-gcc (GCC) 12.1.0 reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/intel-lab-lkp/linux/commit/e5977c421331e16237bf3ebd283981... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Takashi-Iwai/ALSA-Defer-async-signal-handling/20220726-233840 git checkout e5977c421331e16237bf3ebd283981757e03f433 # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=csky SHELL=/bin/bash sound/core/
If you fix the issue, kindly add following tag where applicable Reported-by: kernel test robot lkp@intel.com
All errors (new ones prefixed by >>):
sound/core/misc.c: In function 'snd_fasync_work_fn':
sound/core/misc.c:180:25: error: implicit declaration of function 'kill_fasync'; did you mean 'snd_kill_fasync'? [-Werror=implicit-function-declaration]
180 | kill_fasync(&fasync->fasync, fasync->signal, fasync->poll); | ^~~~~~~~~~~ | snd_kill_fasync sound/core/misc.c: In function 'snd_fasync_helper':
sound/core/misc.c:213:16: error: implicit declaration of function 'fasync_helper'; did you mean 'snd_fasync_helper'? [-Werror=implicit-function-declaration]
213 | return fasync_helper(fd, file, on, &fasync->fasync); | ^~~~~~~~~~~~~ | snd_fasync_helper cc1: some warnings being treated as errors
vim +180 sound/core/misc.c
169 170 static void snd_fasync_work_fn(struct work_struct *work) 171 { 172 struct snd_fasync *fasync; 173 174 spin_lock_irq(&snd_fasync_lock); 175 while (!list_empty(&snd_fasync_list)) { 176 fasync = list_first_entry(&snd_fasync_list, struct snd_fasync, list); 177 list_del_init(&fasync->list); 178 spin_unlock_irq(&snd_fasync_lock); 179 if (fasync->on)
180 kill_fasync(&fasync->fasync, fasync->signal, fasync->poll);
181 spin_lock_irq(&snd_fasync_lock); 182 } 183 spin_unlock_irq(&snd_fasync_lock); 184 } 185 186 static DECLARE_WORK(snd_fasync_work, snd_fasync_work_fn); 187 188 int snd_fasync_helper(int fd, struct file *file, int on, 189 struct snd_fasync **fasyncp) 190 { 191 struct snd_fasync *fasync = NULL; 192 193 if (on) { 194 fasync = kzalloc(sizeof(*fasync), GFP_KERNEL); 195 if (!fasync) 196 return -ENOMEM; 197 INIT_LIST_HEAD(&fasync->list); 198 } 199 200 spin_lock_irq(&snd_fasync_lock); 201 if (*fasyncp) { 202 kfree(fasync); 203 fasync = *fasyncp; 204 } else { 205 if (!fasync) { 206 spin_unlock_irq(&snd_fasync_lock); 207 return 0; 208 } 209 *fasyncp = fasync; 210 } 211 fasync->on = on; 212 spin_unlock_irq(&snd_fasync_lock);
213 return fasync_helper(fd, file, on, &fasync->fasync);
214 } 215 EXPORT_SYMBOL_GPL(snd_fasync_helper); 216
On Wed, 27 Jul 2022 03:14:53 +0200, kernel test robot wrote:
Hi Takashi,
I love your patch! Yet something to improve:
[auto build test ERROR on tiwai-sound/for-next] [also build test ERROR on linus/master v5.19-rc8 next-20220726] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Takashi-Iwai/ALSA-Defer-async... base: https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git for-next config: csky-randconfig-r014-20220726 (https://download.01.org/0day-ci/archive/20220727/202207270950.zWfcvyEK-lkp@i...) compiler: csky-linux-gcc (GCC) 12.1.0 reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/intel-lab-lkp/linux/commit/e5977c421331e16237bf3ebd283981... git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Takashi-Iwai/ALSA-Defer-async-signal-handling/20220726-233840 git checkout e5977c421331e16237bf3ebd283981757e03f433 # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=csky SHELL=/bin/bash sound/core/
If you fix the issue, kindly add following tag where applicable Reported-by: kernel test robot lkp@intel.com
All errors (new ones prefixed by >>):
sound/core/misc.c: In function 'snd_fasync_work_fn':
sound/core/misc.c:180:25: error: implicit declaration of function 'kill_fasync'; did you mean 'snd_kill_fasync'? [-Werror=implicit-function-declaration]
It needs the inclusion of linux/fs.h. Will fix up in v2 series.
Takashi
For avoiding the potential deadlock via kill_fasync() call, use the new fasync helpers to defer the invocation from PCI API. Note that it's merely a workaround.
Reported-by: syzbot+1ee0910eca9c94f71f25@syzkaller.appspotmail.com Reported-by: syzbot+49b10793b867871ee26f@syzkaller.appspotmail.com Reported-by: syzbot+8285e973a41b5aa68902@syzkaller.appspotmail.com Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/core/timer.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/sound/core/timer.c b/sound/core/timer.c index b3214baa8919..e08a37c23add 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -83,7 +83,7 @@ struct snd_timer_user { unsigned int filter; struct timespec64 tstamp; /* trigger tstamp */ wait_queue_head_t qchange_sleep; - struct fasync_struct *fasync; + struct snd_fasync *fasync; struct mutex ioctl_lock; };
@@ -1345,7 +1345,7 @@ static void snd_timer_user_interrupt(struct snd_timer_instance *timeri, } __wake: spin_unlock(&tu->qlock); - kill_fasync(&tu->fasync, SIGIO, POLL_IN); + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); wake_up(&tu->qchange_sleep); }
@@ -1383,7 +1383,7 @@ static void snd_timer_user_ccallback(struct snd_timer_instance *timeri, spin_lock_irqsave(&tu->qlock, flags); snd_timer_user_append_to_tqueue(tu, &r1); spin_unlock_irqrestore(&tu->qlock, flags); - kill_fasync(&tu->fasync, SIGIO, POLL_IN); + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); wake_up(&tu->qchange_sleep); }
@@ -1453,7 +1453,7 @@ static void snd_timer_user_tinterrupt(struct snd_timer_instance *timeri, spin_unlock(&tu->qlock); if (append == 0) return; - kill_fasync(&tu->fasync, SIGIO, POLL_IN); + snd_kill_fasync(tu->fasync, SIGIO, POLL_IN); wake_up(&tu->qchange_sleep); }
@@ -1521,6 +1521,7 @@ static int snd_timer_user_release(struct inode *inode, struct file *file) snd_timer_instance_free(tu->timeri); } mutex_unlock(&tu->ioctl_lock); + snd_fasync_free(tu->fasync); kfree(tu->queue); kfree(tu->tqueue); kfree(tu); @@ -2135,7 +2136,7 @@ static int snd_timer_user_fasync(int fd, struct file * file, int on) struct snd_timer_user *tu;
tu = file->private_data; - return fasync_helper(fd, file, on, &tu->fasync); + return snd_fasync_helper(fd, file, on, &tu->fasync); }
static ssize_t snd_timer_user_read(struct file *file, char __user *buffer,
For avoiding the potential deadlock via kill_fasync() call, use the new fasync helpers to defer the invocation from timer API. Note that it's merely a workaround.
Reported-by: syzbot+8285e973a41b5aa68902@syzkaller.appspotmail.com Reported-by: syzbot+669c9abf11a6a011dd09@syzkaller.appspotmail.com Signed-off-by: Takashi Iwai tiwai@suse.de --- include/sound/pcm.h | 2 +- sound/core/pcm.c | 1 + sound/core/pcm_lib.c | 2 +- sound/core/pcm_native.c | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 2d03c10f6a36..8c48a5bce88c 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -399,7 +399,7 @@ struct snd_pcm_runtime { snd_pcm_uframes_t twake; /* do transfer (!poll) wakeup if non-zero */ wait_queue_head_t sleep; /* poll sleep */ wait_queue_head_t tsleep; /* transfer sleep */ - struct fasync_struct *fasync; + struct snd_fasync *fasync; bool stop_operating; /* sync_stop will be called */ struct mutex buffer_mutex; /* protect for buffer changes */ atomic_t buffer_accessing; /* >0: in r/w operation, <0: blocked */ diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 03fc5fa5813e..2ac742035310 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -1007,6 +1007,7 @@ void snd_pcm_detach_substream(struct snd_pcm_substream *substream) substream->runtime = NULL; } mutex_destroy(&runtime->buffer_mutex); + snd_fasync_free(runtime->fasync); kfree(runtime); put_pid(substream->pid); substream->pid = NULL; diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 1fc7c50ffa62..40751e5aff09 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -1822,7 +1822,7 @@ void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *substrea snd_timer_interrupt(substream->timer, 1); #endif _end: - kill_fasync(&runtime->fasync, SIGIO, POLL_IN); + snd_kill_fasync(runtime->fasync, SIGIO, POLL_IN); } EXPORT_SYMBOL(snd_pcm_period_elapsed_under_stream_lock);
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index aa0453e51595..ad0541e9e888 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3951,7 +3951,7 @@ static int snd_pcm_fasync(int fd, struct file * file, int on) runtime = substream->runtime; if (runtime->status->state == SNDRV_PCM_STATE_DISCONNECTED) return -EBADFD; - return fasync_helper(fd, file, on, &runtime->fasync); + return snd_fasync_helper(fd, file, on, &runtime->fasync); }
/*
For avoiding the potential deadlock via kill_fasync() call, use the new fasync helpers to defer the invocation from the control API. Note that it's merely a workaround.
Another note: although we haven't received reports about the deadlock with the control API, the deadlock is still potentially possible, and it's better to align the behavior with other core APIs (PCM and timer); so let's move altogether.
Signed-off-by: Takashi Iwai tiwai@suse.de --- include/sound/control.h | 2 +- sound/core/control.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/include/sound/control.h b/include/sound/control.h index fcd3cce673ec..eae443ba79ba 100644 --- a/include/sound/control.h +++ b/include/sound/control.h @@ -109,7 +109,7 @@ struct snd_ctl_file { int preferred_subdevice[SND_CTL_SUBDEV_ITEMS]; wait_queue_head_t change_sleep; spinlock_t read_lock; - struct fasync_struct *fasync; + struct snd_fasync *fasync; int subscribed; /* read interface is activated */ struct list_head events; /* waiting events for read */ }; diff --git a/sound/core/control.c b/sound/core/control.c index 4dba3a342458..f3e893715369 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -127,6 +127,7 @@ static int snd_ctl_release(struct inode *inode, struct file *file) if (control->vd[idx].owner == ctl) control->vd[idx].owner = NULL; up_write(&card->controls_rwsem); + snd_fasync_free(ctl->fasync); snd_ctl_empty_read_queue(ctl); put_pid(ctl->pid); kfree(ctl); @@ -181,7 +182,7 @@ void snd_ctl_notify(struct snd_card *card, unsigned int mask, _found: wake_up(&ctl->change_sleep); spin_unlock(&ctl->read_lock); - kill_fasync(&ctl->fasync, SIGIO, POLL_IN); + snd_kill_fasync(ctl->fasync, SIGIO, POLL_IN); } read_unlock_irqrestore(&card->ctl_files_rwlock, flags); } @@ -2134,7 +2135,7 @@ static int snd_ctl_fasync(int fd, struct file * file, int on) struct snd_ctl_file *ctl;
ctl = file->private_data; - return fasync_helper(fd, file, on, &ctl->fasync); + return snd_fasync_helper(fd, file, on, &ctl->fasync); }
/* return the preferred subdevice number if already assigned; @@ -2302,7 +2303,7 @@ static int snd_ctl_dev_disconnect(struct snd_device *device) read_lock_irqsave(&card->ctl_files_rwlock, flags); list_for_each_entry(ctl, &card->ctl_files, list) { wake_up(&ctl->change_sleep); - kill_fasync(&ctl->fasync, SIGIO, POLL_ERR); + snd_kill_fasync(ctl->fasync, SIGIO, POLL_ERR); } read_unlock_irqrestore(&card->ctl_files_rwlock, flags);
participants (2)
-
kernel test robot
-
Takashi Iwai