Skip to content

Commit ef34a0a

Browse files
committed
ALSA: core: Add async signal helpers
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/[email protected] Signed-off-by: Takashi Iwai <[email protected]>
1 parent 87eb04b commit ef34a0a

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

include/sound/core.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,4 +507,12 @@ snd_pci_quirk_lookup_id(u16 vendor, u16 device,
507507
}
508508
#endif
509509

510+
/* async signal helpers */
511+
struct snd_fasync;
512+
513+
int snd_fasync_helper(int fd, struct file *file, int on,
514+
struct snd_fasync **fasyncp);
515+
void snd_kill_fasync(struct snd_fasync *fasync, int signal, int poll);
516+
void snd_fasync_free(struct snd_fasync *fasync);
517+
510518
#endif /* __SOUND_CORE_H */

sound/core/misc.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <linux/time.h>
1111
#include <linux/slab.h>
1212
#include <linux/ioport.h>
13+
#include <linux/fs.h>
1314
#include <sound/core.h>
1415

1516
#ifdef CONFIG_SND_DEBUG
@@ -145,3 +146,96 @@ snd_pci_quirk_lookup(struct pci_dev *pci, const struct snd_pci_quirk *list)
145146
}
146147
EXPORT_SYMBOL(snd_pci_quirk_lookup);
147148
#endif
149+
150+
/*
151+
* Deferred async signal helpers
152+
*
153+
* Below are a few helper functions to wrap the async signal handling
154+
* in the deferred work. The main purpose is to avoid the messy deadlock
155+
* around tasklist_lock and co at the kill_fasync() invocation.
156+
* fasync_helper() and kill_fasync() are replaced with snd_fasync_helper()
157+
* and snd_kill_fasync(), respectively. In addition, snd_fasync_free() has
158+
* to be called at releasing the relevant file object.
159+
*/
160+
struct snd_fasync {
161+
struct fasync_struct *fasync;
162+
int signal;
163+
int poll;
164+
int on;
165+
struct list_head list;
166+
};
167+
168+
static DEFINE_SPINLOCK(snd_fasync_lock);
169+
static LIST_HEAD(snd_fasync_list);
170+
171+
static void snd_fasync_work_fn(struct work_struct *work)
172+
{
173+
struct snd_fasync *fasync;
174+
175+
spin_lock_irq(&snd_fasync_lock);
176+
while (!list_empty(&snd_fasync_list)) {
177+
fasync = list_first_entry(&snd_fasync_list, struct snd_fasync, list);
178+
list_del_init(&fasync->list);
179+
spin_unlock_irq(&snd_fasync_lock);
180+
if (fasync->on)
181+
kill_fasync(&fasync->fasync, fasync->signal, fasync->poll);
182+
spin_lock_irq(&snd_fasync_lock);
183+
}
184+
spin_unlock_irq(&snd_fasync_lock);
185+
}
186+
187+
static DECLARE_WORK(snd_fasync_work, snd_fasync_work_fn);
188+
189+
int snd_fasync_helper(int fd, struct file *file, int on,
190+
struct snd_fasync **fasyncp)
191+
{
192+
struct snd_fasync *fasync = NULL;
193+
194+
if (on) {
195+
fasync = kzalloc(sizeof(*fasync), GFP_KERNEL);
196+
if (!fasync)
197+
return -ENOMEM;
198+
INIT_LIST_HEAD(&fasync->list);
199+
}
200+
201+
spin_lock_irq(&snd_fasync_lock);
202+
if (*fasyncp) {
203+
kfree(fasync);
204+
fasync = *fasyncp;
205+
} else {
206+
if (!fasync) {
207+
spin_unlock_irq(&snd_fasync_lock);
208+
return 0;
209+
}
210+
*fasyncp = fasync;
211+
}
212+
fasync->on = on;
213+
spin_unlock_irq(&snd_fasync_lock);
214+
return fasync_helper(fd, file, on, &fasync->fasync);
215+
}
216+
EXPORT_SYMBOL_GPL(snd_fasync_helper);
217+
218+
void snd_kill_fasync(struct snd_fasync *fasync, int signal, int poll)
219+
{
220+
unsigned long flags;
221+
222+
if (!fasync || !fasync->on)
223+
return;
224+
spin_lock_irqsave(&snd_fasync_lock, flags);
225+
fasync->signal = signal;
226+
fasync->poll = poll;
227+
list_move(&fasync->list, &snd_fasync_list);
228+
schedule_work(&snd_fasync_work);
229+
spin_unlock_irqrestore(&snd_fasync_lock, flags);
230+
}
231+
EXPORT_SYMBOL_GPL(snd_kill_fasync);
232+
233+
void snd_fasync_free(struct snd_fasync *fasync)
234+
{
235+
if (!fasync)
236+
return;
237+
fasync->on = 0;
238+
flush_work(&snd_fasync_work);
239+
kfree(fasync);
240+
}
241+
EXPORT_SYMBOL_GPL(snd_fasync_free);

0 commit comments

Comments
 (0)