Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

ALSA: aloop: Fix peer runtime UAF during format-change stop

loopback_check_format() may stop the capture side when playback starts
with parameters that no longer match a running capture stream. Commit
826af7fa62e3 ("ALSA: aloop: Fix racy access at PCM trigger") moved
the peer lookup under cable->lock, but the actual snd_pcm_stop() still
runs after dropping that lock.

A concurrent close can clear the capture entry from cable->streams[] and
detach or free its runtime while the playback trigger path still holds a
stale peer substream pointer.

Keep a per-cable count of in-flight peer stops before dropping
cable->lock, and make free_cable() wait for those stops before
detaching the runtime. This preserves the existing behavior while
making the peer runtime lifetime explicit.

Reported-by: syzbot+8fa95c41eafbc9d2ff6f@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=8fa95c41eafbc9d2ff6f
Fixes: 597603d615d2 ("ALSA: introduce the snd-aloop module for the PCM loopback")
Cc: stable@vger.kernel.org
Suggested-by: Takashi Iwai <tiwai@suse.com>
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
Link: https://patch.msgid.link/20260424-alsa-aloop-peer-stop-uaf-v2-1-94e68101db8a@gmail.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>

authored by

Cássio Gabriel and committed by
Takashi Iwai
e5c33cdc 901ac0ff

+30 -13
+30 -13
sound/drivers/aloop.c
··· 99 99 struct loopback_cable { 100 100 spinlock_t lock; 101 101 struct loopback_pcm *streams[2]; 102 + /* in-flight peer stops running outside cable->lock */ 103 + atomic_t stop_count; 104 + wait_queue_head_t stop_wait; 102 105 struct snd_pcm_hardware hw; 103 106 /* flags */ 104 107 unsigned int valid; ··· 369 366 return 0; 370 367 if (stream == SNDRV_PCM_STREAM_CAPTURE) 371 368 return -EIO; 372 - else if (cruntime->state == SNDRV_PCM_STATE_RUNNING) 369 + else if (cruntime->state == SNDRV_PCM_STATE_RUNNING) { 370 + /* close must not free the peer runtime below */ 371 + atomic_inc(&cable->stop_count); 373 372 stop_capture = true; 373 + } 374 374 } 375 375 376 376 setup = get_setup(dpcm_play); ··· 402 396 } 403 397 } 404 398 405 - if (stop_capture) 399 + if (stop_capture) { 406 400 snd_pcm_stop(dpcm_capt->substream, SNDRV_PCM_STATE_DRAINING); 401 + if (atomic_dec_and_test(&cable->stop_count)) 402 + wake_up(&cable->stop_wait); 403 + } 407 404 408 405 return 0; 409 406 } ··· 1058 1049 struct loopback *loopback = substream->private_data; 1059 1050 int dev = get_cable_index(substream); 1060 1051 struct loopback_cable *cable; 1052 + struct loopback_pcm *dpcm; 1053 + bool other_alive; 1061 1054 1062 1055 cable = loopback->cables[substream->number][dev]; 1063 1056 if (!cable) 1064 1057 return; 1065 - if (cable->streams[!substream->stream]) { 1066 - /* other stream is still alive */ 1067 - guard(spinlock_irq)(&cable->lock); 1068 - cable->streams[substream->stream] = NULL; 1069 - } else { 1070 - struct loopback_pcm *dpcm = substream->runtime->private_data; 1071 1058 1072 - if (cable->ops && cable->ops->close_cable && dpcm) 1073 - cable->ops->close_cable(dpcm); 1074 - /* free the cable */ 1075 - loopback->cables[substream->number][dev] = NULL; 1076 - kfree(cable); 1059 + scoped_guard(spinlock_irq, &cable->lock) { 1060 + cable->streams[substream->stream] = NULL; 1061 + other_alive = cable->streams[!substream->stream]; 1077 1062 } 1063 + 1064 + /* Pair with the stop_count increment in loopback_check_format(). */ 1065 + wait_event(cable->stop_wait, !atomic_read(&cable->stop_count)); 1066 + if (other_alive) 1067 + return; 1068 + 1069 + dpcm = substream->runtime->private_data; 1070 + if (cable->ops && cable->ops->close_cable && dpcm) 1071 + cable->ops->close_cable(dpcm); 1072 + /* free the cable */ 1073 + loopback->cables[substream->number][dev] = NULL; 1074 + kfree(cable); 1078 1075 } 1079 1076 1080 1077 static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm) ··· 1275 1260 goto unlock; 1276 1261 } 1277 1262 spin_lock_init(&cable->lock); 1263 + atomic_set(&cable->stop_count, 0); 1264 + init_waitqueue_head(&cable->stop_wait); 1278 1265 cable->hw = loopback_pcm_hardware; 1279 1266 if (loopback->timer_source) 1280 1267 cable->ops = &loopback_snd_timer_ops;