lkml.org 
[lkml]   [2022]   [Aug]   [14]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH AUTOSEL 5.18 29/39] ALSA: core: Add async signal helpers
    Date
    From: Takashi Iwai <tiwai@suse.de>

    [ Upstream commit ef34a0ae7a2654bc9e58675e36898217fb2799d8 ]

    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.

    Link: https://lore.kernel.org/r/20220728125945.29533-2-tiwai@suse.de
    Signed-off-by: Takashi Iwai <tiwai@suse.de>
    Signed-off-by: Sasha Levin <sashal@kernel.org>
    ---
    include/sound/core.h | 8 ++++
    sound/core/misc.c | 94 ++++++++++++++++++++++++++++++++++++++++++++
    2 files changed, 102 insertions(+)

    diff --git a/include/sound/core.h b/include/sound/core.h
    index 6d4cc49584c6..39cee40ac22e 100644
    --- a/include/sound/core.h
    +++ b/include/sound/core.h
    @@ -501,4 +501,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..d32a19976a2b 100644
    --- a/sound/core/misc.c
    +++ b/sound/core/misc.c
    @@ -10,6 +10,7 @@
    #include <linux/time.h>
    #include <linux/slab.h>
    #include <linux/ioport.h>
    +#include <linux/fs.h>
    #include <sound/core.h>

    #ifdef CONFIG_SND_DEBUG
    @@ -145,3 +146,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);
    --
    2.35.1
    \
     
     \ /
      Last update: 2022-08-14 18:34    [W:3.975 / U:0.032 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site