]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ALSA: aloop: Fix racy access at PCM trigger
authorTakashi Iwai <tiwai@suse.de>
Tue, 3 Feb 2026 14:09:59 +0000 (15:09 +0100)
committerTakashi Iwai <tiwai@suse.de>
Tue, 3 Feb 2026 14:22:21 +0000 (15:22 +0100)
The PCM trigger callback of aloop driver tries to check the PCM state
and stop the stream of the tied substream in the corresponding cable.
Since both check and stop operations are performed outside the cable
lock, this may result in UAF when a program attempts to trigger
frequently while opening/closing the tied stream, as spotted by
fuzzers.

For addressing the UAF, this patch changes two things:
- It covers the most of code in loopback_check_format() with
  cable->lock spinlock, and add the proper NULL checks.  This avoids
  already some racy accesses.
- In addition, now we try to check the state of the capture PCM stream
  that may be stopped in this function, which was the major pain point
  leading to UAF.

Reported-by: syzbot+5f8f3acdee1ec7a7ef7b@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/69783ba1.050a0220.c9109.0011.GAE@google.com
Cc: <stable@vger.kernel.org>
Link: https://patch.msgid.link/20260203141003.116584-1-tiwai@suse.de
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/drivers/aloop.c

index 64ef03b2d579c9cbf84a2fd52c32246938680c8e..aa0d2fcb1a180ca149afd2695cf270b02757220d 100644 (file)
@@ -336,37 +336,43 @@ static bool is_access_interleaved(snd_pcm_access_t access)
 
 static int loopback_check_format(struct loopback_cable *cable, int stream)
 {
+       struct loopback_pcm *dpcm_play, *dpcm_capt;
        struct snd_pcm_runtime *runtime, *cruntime;
        struct loopback_setup *setup;
        struct snd_card *card;
+       bool stop_capture = false;
        int check;
 
-       if (cable->valid != CABLE_VALID_BOTH) {
-               if (stream == SNDRV_PCM_STREAM_PLAYBACK)
-                       goto __notify;
-               return 0;
-       }
-       runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->
-                                                       substream->runtime;
-       cruntime = cable->streams[SNDRV_PCM_STREAM_CAPTURE]->
-                                                       substream->runtime;
-       check = runtime->format != cruntime->format ||
-               runtime->rate != cruntime->rate ||
-               runtime->channels != cruntime->channels ||
-               is_access_interleaved(runtime->access) !=
-               is_access_interleaved(cruntime->access);
-       if (!check)
-               return 0;
-       if (stream == SNDRV_PCM_STREAM_CAPTURE) {
-               return -EIO;
-       } else {
-               snd_pcm_stop(cable->streams[SNDRV_PCM_STREAM_CAPTURE]->
-                                       substream, SNDRV_PCM_STATE_DRAINING);
-             __notify:
-               runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->
-                                                       substream->runtime;
-               setup = get_setup(cable->streams[SNDRV_PCM_STREAM_PLAYBACK]);
-               card = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->loopback->card;
+       scoped_guard(spinlock_irqsave, &cable->lock) {
+               dpcm_play = cable->streams[SNDRV_PCM_STREAM_PLAYBACK];
+               dpcm_capt = cable->streams[SNDRV_PCM_STREAM_CAPTURE];
+
+               if (cable->valid != CABLE_VALID_BOTH) {
+                       if (stream == SNDRV_PCM_STREAM_CAPTURE || !dpcm_play)
+                               return 0;
+               } else {
+                       if (!dpcm_play || !dpcm_capt)
+                               return -EIO;
+                       runtime = dpcm_play->substream->runtime;
+                       cruntime = dpcm_capt->substream->runtime;
+                       if (!runtime || !cruntime)
+                               return -EIO;
+                       check = runtime->format != cruntime->format ||
+                       runtime->rate != cruntime->rate ||
+                       runtime->channels != cruntime->channels ||
+                       is_access_interleaved(runtime->access) !=
+                       is_access_interleaved(cruntime->access);
+                       if (!check)
+                               return 0;
+                       if (stream == SNDRV_PCM_STREAM_CAPTURE)
+                               return -EIO;
+                       else if (cruntime->state == SNDRV_PCM_STATE_RUNNING)
+                               stop_capture = true;
+               }
+
+               setup = get_setup(dpcm_play);
+               card = dpcm_play->loopback->card;
+               runtime = dpcm_play->substream->runtime;
                if (setup->format != runtime->format) {
                        snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE,
                                                        &setup->format_id);
@@ -389,6 +395,10 @@ static int loopback_check_format(struct loopback_cable *cable, int stream)
                        setup->access = runtime->access;
                }
        }
+
+       if (stop_capture)
+               snd_pcm_stop(dpcm_capt->substream, SNDRV_PCM_STATE_DRAINING);
+
        return 0;
 }