AACI driver bug

From: Саня Пронський

Date: Thu Apr 30 2026 - 16:01:59 EST


Hello,
I've noticed a potential race condition in the AACI driver (sound/arm/aaci.c).
The current implementation of aaci_fifo_irq() accesses the substream pointer
outside the spinlock-protected area, which could lead to a NULL pointer
dereference or use-after-free if the substream is released in a parallel thread.
This patch ensures the substream pointer is safely captured under the lock
before use.
Best regards,
Alexander Pronskiy
From: Alexander Pronskiy <sanapronskij96@xxxxxxxxx>
Date: Thu, 30 Apr 2026 22:45:00 +0300
Subject: [PATCH] ALSA: aaci: Fix potential use-after-free in aaci_fifo_irq

There is a race condition in aaci_fifo_irq() between the interrupt handler
and the PCM close/stop operations. The function checks for the existence
of aacirun->substream at the beginning of each interrupt block, but the
actual call to snd_pcm_period_elapsed() occurs outside the spinlock
protected area.

If the substream is released and nulled in another thread (e.g., during
aaci_pcm_close) after the spinlock is released but before the callback is
executed, it leads to a NULL pointer dereference or use-after-free.

This patch fixes the issue by capturing the substream pointer into a
local variable while holding the spinlock and using that local variable
for the callback only if it was valid during the critical section.

Signed-off-by: Alexander Pronskiy <sanapronskij96@xxxxxxxxx>
---
sound/arm/aaci.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c
index d6543b1..e822a12 100644
--- a/sound/arm/aaci.c
+++ b/sound/arm/aaci.c
@@ -155,6 +155,7 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
if (mask & ISR_RXINTR) {
struct aaci_runtime *aacirun = &aaci->capture;
bool period_elapsed = false;
+ struct snd_pcm_substream *substream;
void *ptr;

if (!aacirun->substream || !aacirun->start) {
@@ -164,6 +165,8 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
}

scoped_guard(spinlock, &aacirun->lock) {
+ substream = aacirun->substream;
+ if (!substream)
+ return;
+
ptr = aacirun->ptr;
do {
unsigned int len = aacirun->fifo_bytes;
@@ -204,8 +207,8 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
aacirun->ptr = ptr;
}

- if (period_elapsed)
- snd_pcm_period_elapsed(aacirun->substream);
+ if (period_elapsed && substream)
+ snd_pcm_period_elapsed(substream);
}

if (mask & ISR_URINTR) {
@@ -216,6 +219,7 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
if (mask & ISR_TXINTR) {
struct aaci_runtime *aacirun = &aaci->playback;
bool period_elapsed = false;
+ struct snd_pcm_substream *substream;
void *ptr;

if (!aacirun->substream || !aacirun->start) {
@@ -225,6 +229,8 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
}

scoped_guard(spinlock, &aacirun->lock) {
+ substream = aacirun->substream;
+ if (!substream)
+ return;
+
ptr = aacirun->ptr;
do {
unsigned int len = aacirun->fifo_bytes;
@@ -265,8 +271,8 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
aacirun->ptr = ptr;
}

- if (period_elapsed)
- snd_pcm_period_elapsed(aacirun->substream);
+ if (period_elapsed && substream)
+ snd_pcm_period_elapsed(substream);
}
}
--
2.43.0