Skip to content

Commit 051e084

Browse files
stonezdmtiwai
authored andcommitted
ALSA: sh: aica: reorder cleanup operations to avoid UAF bugs
The dreamcastcard->timer could schedule the spu_dma_work and the spu_dma_work could also arm the dreamcastcard->timer. When the snd_pcm_substream is closing, the aica_channel will be deallocated. But it could still be dereferenced in the worker thread. The reason is that del_timer() will return directly regardless of whether the timer handler is running or not and the worker could be rescheduled in the timer handler. As a result, the UAF bug will happen. The racy situation is shown below: (Thread 1) | (Thread 2) snd_aicapcm_pcm_close() | ... | run_spu_dma() //worker | mod_timer() flush_work() | del_timer() | aica_period_elapsed() //timer kfree(dreamcastcard->channel) | schedule_work() | run_spu_dma() //worker ... | dreamcastcard->channel-> //USE In order to mitigate this bug and other possible corner cases, call mod_timer() conditionally in run_spu_dma(), then implement PCM sync_stop op to cancel both the timer and worker. The sync_stop op will be called from PCM core appropriately when needed. Fixes: 198de43 ("[ALSA] Add ALSA support for the SEGA Dreamcast PCM device") Suggested-by: Takashi Iwai <[email protected]> Signed-off-by: Duoming Zhou <[email protected]> Message-ID: <[email protected]> Signed-off-by: Takashi Iwai <[email protected]>
1 parent cafe9c6 commit 051e084

File tree

1 file changed

+14
-3
lines changed

1 file changed

+14
-3
lines changed

sound/sh/aica.c

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ static void run_spu_dma(struct work_struct *work)
278278
dreamcastcard->clicks++;
279279
if (unlikely(dreamcastcard->clicks >= AICA_PERIOD_NUMBER))
280280
dreamcastcard->clicks %= AICA_PERIOD_NUMBER;
281-
mod_timer(&dreamcastcard->timer, jiffies + 1);
281+
if (snd_pcm_running(dreamcastcard->substream))
282+
mod_timer(&dreamcastcard->timer, jiffies + 1);
282283
}
283284
}
284285

@@ -290,6 +291,8 @@ static void aica_period_elapsed(struct timer_list *t)
290291
/*timer function - so cannot sleep */
291292
int play_period;
292293
struct snd_pcm_runtime *runtime;
294+
if (!snd_pcm_running(substream))
295+
return;
293296
runtime = substream->runtime;
294297
dreamcastcard = substream->pcm->private_data;
295298
/* Have we played out an additional period? */
@@ -350,12 +353,19 @@ static int snd_aicapcm_pcm_open(struct snd_pcm_substream
350353
return 0;
351354
}
352355

356+
static int snd_aicapcm_pcm_sync_stop(struct snd_pcm_substream *substream)
357+
{
358+
struct snd_card_aica *dreamcastcard = substream->pcm->private_data;
359+
360+
del_timer_sync(&dreamcastcard->timer);
361+
cancel_work_sync(&dreamcastcard->spu_dma_work);
362+
return 0;
363+
}
364+
353365
static int snd_aicapcm_pcm_close(struct snd_pcm_substream
354366
*substream)
355367
{
356368
struct snd_card_aica *dreamcastcard = substream->pcm->private_data;
357-
flush_work(&(dreamcastcard->spu_dma_work));
358-
del_timer(&dreamcastcard->timer);
359369
dreamcastcard->substream = NULL;
360370
kfree(dreamcastcard->channel);
361371
spu_disable();
@@ -401,6 +411,7 @@ static const struct snd_pcm_ops snd_aicapcm_playback_ops = {
401411
.prepare = snd_aicapcm_pcm_prepare,
402412
.trigger = snd_aicapcm_pcm_trigger,
403413
.pointer = snd_aicapcm_pcm_pointer,
414+
.sync_stop = snd_aicapcm_pcm_sync_stop,
404415
};
405416

406417
/* TO DO: set up to handle more than one pcm instance */

0 commit comments

Comments
 (0)