FAILED: patch "[PATCH] ALSA: aloop: Fix peer runtime UAF during format-change stop" failed to apply to 6.6-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x e5c33cdc6f402eab8abd36ecf436b22c9d3a8aff
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026050445-detention-cussed-a8c0@gregkh' --subject-prefix 'PATCH 6.6.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From e5c33cdc6f402eab8abd36ecf436b22c9d3a8aff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A1ssio=20Gabriel?= <[email protected]>
Date: Fri, 24 Apr 2026 09:48:41 -0300
Subject: [PATCH] ALSA: aloop: Fix peer runtime UAF during format-change stop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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.
sound/drivers/aloop.c +30 -13
@@ -99,6 +99,9 @@ struct loopback_ops {struct loopback_cable {spinlock_t lock;struct loopback_pcm *streams[2 ]; +/* in-flight peer stops running outside cable->lock */ +atomic_t stop_count; +wait_queue_head_t stop_wait;struct snd_pcm_hardware hw;/* flags */ unsigned int valid;
@@ -366,8 +369,11 @@ static int loopback_check_format(struct loopback_cable *cable, int stream)return 0 ;if (stream ==SNDRV_PCM_STREAM_CAPTURE )return -EIO ; -else if (cruntime->state ==SNDRV_PCM_STATE_RUNNING ) +else if (cruntime->state ==SNDRV_PCM_STATE_RUNNING ) { +/* close must not free the peer runtime below */ +atomic_inc (&cable->stop_count); stop_capture = true; + } } setup =get_setup (dpcm_play);
@@ -396,8 +402,11 @@ static int loopback_check_format(struct loopback_cable *cable, int stream) } } -if (stop_capture) +if (stop_capture) {snd_pcm_stop (dpcm_capt->substream,SNDRV_PCM_STATE_DRAINING ); +if (atomic_dec_and_test (&cable->stop_count)) +wake_up (&cable->stop_wait); + }return 0 ; }
@@ -1049,23 +1058,29 @@ static void free_cable(struct snd_pcm_substream *substream)struct loopback *loopback = substream->private_data;int dev =get_cable_index (substream);struct loopback_cable *cable; +struct loopback_pcm *dpcm; +bool other_alive; cable = loopback->cables[substream->number][dev];if (!cable)return ; -if (cable->streams[!substream->stream]) { -/* other stream is still alive */ -guard (spinlock_irq)(&cable->lock); - cable->streams[substream->stream] =NULL ; - }else { -struct loopback_pcm *dpcm = substream->runtime->private_data; -if (cable->ops && cable->ops->close_cable && dpcm) - cable->ops->close_cable (dpcm); -/* free the cable */ - loopback->cables[substream->number][dev] =NULL ; -kfree (cable); + scoped_guard(spinlock_irq, &cable->lock) { + cable->streams[substream->stream] =NULL ; + other_alive = cable->streams[!substream->stream]; } + +/* Pair with the stop_count increment in loopback_check_format(). */ +wait_event (cable->stop_wait, !atomic_read (&cable->stop_count)); +if (other_alive) +return ; + + dpcm = substream->runtime->private_data; +if (cable->ops && cable->ops->close_cable && dpcm) + cable->ops->close_cable (dpcm); +/* free the cable */ + loopback->cables[substream->number][dev] =NULL ; +kfree (cable); }static int loopback_jiffies_timer_open (struct loopback_pcm *dpcm)
@@ -1260,6 +1275,8 @@ static int loopback_open(struct snd_pcm_substream *substream) goto unlock; }spin_lock_init (&cable->lock); +atomic_set (&cable->stop_count,0 ); +init_waitqueue_head (&cable->stop_wait); cable->hw = loopback_pcm_hardware;if (loopback->timer_source) cable->ops = &loopback_snd_timer_ops;
cover ALSA: aloop: fix trigger/close UAFs
Backport the two ordered aloop fixes for the trigger/close peer-runtime races. Both carry Cc: stable and each fixes a separate syzbot-reported UAF, but neither reached this tree.
826af7fa62e3 ("ALSA: aloop: Fix racy access at PCM trigger") covers the peer lookup and state checks with cable->lock. e5c33cdc6f40 ("ALSA:
aloop: Fix peer runtime UAF during format-change stop") pins an in-flight peer stop across snd_pcm_stop(), which still runs after that lock is dropped, so it applies only on top of the first.
The failed-apply report for this branch named only the second fix.
The series applies to v6.6.155 and v6.6.156-rc1. Patch 1 omits access-mode support absent from this tree; patch 2 matches the 6.12.y backport by patch-id. The identical 6.1.y backport is submitted separately. 6.12.y and newer already contain both fixes.
KASAN/QEMU functional A/B tests passed: 4096 captured frames matched byte-for-byte; control notifications, mismatch handling, and 32 open/close iterations passed; no KASAN, Oops, or WARNING was reported. As upstream, a prepared-but-idle capture remains PREPARED when mismatched playback starts instead of being forced to DRAINING.
Cássio Gabriel (1):
ALSA: aloop: Fix peer runtime UAF during format-change stop
Takashi Iwai (1):
ALSA: aloop: Fix racy access at PCM trigger
sound/drivers/aloop.c | 98 +++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 36 deletions(-)
Signature · 1 line
2.53.0
PATCH 1/2 ALSA: aloop: Fix racy access at PCM trigger
From: Takashi Iwai <[email protected]>
[ Upstream commit 826af7fa62e347464b1b4e0ba2fe19a92438084f ]
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.
[ Karl Mehltretter: dropped the access-mode comparison and notification
(462494565c27, e299a9fd433f, cdac6e1f7164). ]
--- sound/drivers/aloop.c | 58 +++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 24 deletions(-)
sound/drivers/aloop.c +34 -24
@@ -319,35 +319,41 @@ static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm)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; -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; +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);
@@ -364,6 +370,10 @@ static int loopback_check_format(struct loopback_cable *cable, int stream) setup->channels = runtime->channels; } } + +if (stop_capture) +snd_pcm_stop (dpcm_capt->substream,SNDRV_PCM_STATE_DRAINING ); +return 0 ; }
Signature · 1 line
2.53.0
PATCH 2/2 ALSA: aloop: Fix peer runtime UAF during format-change stop
In reply to Karl MehltretterFrom: Cássio Gabriel <[email protected]>
[ Upstream commit e5c33cdc6f402eab8abd36ecf436b22c9d3a8aff ]
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.
[ Karl Mehltretter: 6.12.y commit 03f52a9c1704 applies to 6.1.y/6.6.y
unchanged; identical patch-id. ]
--- sound/drivers/aloop.c | 44 +++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-)
sound/drivers/aloop.c +30 -14
@@ -98,6 +98,9 @@ struct loopback_ops {struct loopback_cable {spinlock_t lock;struct loopback_pcm *streams[2 ]; +/* in-flight peer stops running outside cable->lock */ +atomic_t stop_count; +wait_queue_head_t stop_wait;struct snd_pcm_hardware hw;/* flags */ unsigned int valid;
@@ -347,8 +350,11 @@ static int loopback_check_format(struct loopback_cable *cable, int stream)return 0 ;if (stream ==SNDRV_PCM_STREAM_CAPTURE )return -EIO ; -else if (cruntime->state ==SNDRV_PCM_STATE_RUNNING ) +else if (cruntime->state ==SNDRV_PCM_STATE_RUNNING ) { +/* close must not free the peer runtime below */ +atomic_inc (&cable->stop_count); stop_capture = true; + } } setup =get_setup (dpcm_play);
@@ -371,8 +377,11 @@ static int loopback_check_format(struct loopback_cable *cable, int stream) } } -if (stop_capture) +if (stop_capture) {snd_pcm_stop (dpcm_capt->substream,SNDRV_PCM_STATE_DRAINING ); +if (atomic_dec_and_test (&cable->stop_count)) +wake_up (&cable->stop_wait); + }return 0 ; }
@@ -1004,24 +1013,29 @@ static void free_cable(struct snd_pcm_substream *substream)struct loopback *loopback = substream->private_data;int dev =get_cable_index (substream);struct loopback_cable *cable; +struct loopback_pcm *dpcm; +bool other_alive; cable = loopback->cables[substream->number][dev];if (!cable)return ; -if (cable->streams[!substream->stream]) { -/* other stream is still alive */ -spin_lock_irq (&cable->lock); - cable->streams[substream->stream] =NULL ; -spin_unlock_irq (&cable->lock); - }else { -struct loopback_pcm *dpcm = substream->runtime->private_data; -if (cable->ops && cable->ops->close_cable && dpcm) - cable->ops->close_cable (dpcm); -/* free the cable */ - loopback->cables[substream->number][dev] =NULL ; -kfree (cable); + scoped_guard(spinlock_irq, &cable->lock) { + cable->streams[substream->stream] =NULL ; + other_alive = cable->streams[!substream->stream]; } + +/* Pair with the stop_count increment in loopback_check_format(). */ +wait_event (cable->stop_wait, !atomic_read (&cable->stop_count)); +if (other_alive) +return ; + + dpcm = substream->runtime->private_data; +if (cable->ops && cable->ops->close_cable && dpcm) + cable->ops->close_cable (dpcm); +/* free the cable */ + loopback->cables[substream->number][dev] =NULL ; +kfree (cable); }static int loopback_jiffies_timer_open (struct loopback_pcm *dpcm)
@@ -1216,6 +1230,8 @@ static int loopback_open(struct snd_pcm_substream *substream) goto unlock; }spin_lock_init (&cable->lock); +atomic_set (&cable->stop_count,0 ); +init_waitqueue_head (&cable->stop_wait); cable->hw = loopback_pcm_hardware;if (loopback->timer_source) cable->ops = &loopback_snd_timer_ops;
Signature · 1 line
2.53.0
Re: [PATCH 6.6.y 0/2] ALSA: aloop: fix trigger/close UAFs
In reply to Karl MehltretterQuoted 3 lines
Backport the two ordered aloop fixes for the trigger/close peer-runtime races. Both carry Cc: stable and each fixes a separate syzbot-reported UAF, but neither reached this tree.
Queued for 6.6, thanks.
Signature · 2 lines
Thanks, Sasha