[PATCH 1/6] ALSA: compress: remove illegal state mutation in poll()

Omer Cohen <[email protected]> Fri, 26 Jun 2026 16:47:04 +0300
Newsgroups org.alsa-project.alsa-devel,org.kernel.vger.stable
Message-ID <[email protected]>
snd_compr_poll() transitions runtime->state from DRAINING to SETUP
when it observes a drained stream.  This write is unsynchronized with
snd_compress_wait_for_drain(), which reads runtime->state in its
wait_event condition after the device mutex is dropped.

KCSAN flags this on an arm64 system:

  BUG: KCSAN: data-race in snd_compr_poll / snd_compress_wait_for_drain

  write to 0xffff0000c75c8200 of 4 bytes by task 282 on cpu 3:
   snd_compr_poll+0x29c/0x2c8

  read to 0xffff0000c75c8200 of 4 bytes by task 279 on cpu 1:
   snd_compress_wait_for_drain+0xa4/0x270

  value changed: 0x00000005 -> 0x00000001

poll() is a query operation and should not mutate stream state.  The
DRAINING to SETUP transition is already performed by drivers via
snd_compr_drain_notify() when drain actually completes.  The redundant
transition in poll() has been present since the initial compress
offload implementation but only manifests when poll() and drain run
concurrently from different threads.

Remove the state mutation from poll().  When the stream is DRAINING,
report it as ready so userspace can proceed without altering state.

Reproducer (requires any compress offload device, CONFIG_KCSAN=y,
CONFIG_KCSAN_STRICT=y):

  /* 4 threads on the same compress fd, ~2000 iterations to trigger */
  static int compr_fd;

  void *poll_thread(void *arg) {
      struct pollfd pfd = { .fd = compr_fd, .events = POLLOUT };
      while (!stop) poll(&pfd, 1, 10);
      return NULL;
  }
  void *drain_thread(void *arg) {
      while (!stop) ioctl(compr_fd, SNDRV_COMPRESS_DRAIN);
      return NULL;
  }
  void *stop_thread(void *arg) {
      while (!stop) { usleep(500); ioctl(compr_fd, SNDRV_COMPRESS_STOP); }
      return NULL;
  }
  /* main: open compress dev, SET_PARAMS, write data, START,
   * then spawn all 3 threads + repeat setup in a loop.
   * KCSAN fires within seconds. */

  Full reproducer source available on request.

Fixes: b21c60a4edd2 ("ALSA: core: add support for compress_offload")
Cc: [email protected]
Reported-by: Omer Cohen <[email protected]>
Signed-off-by: Omer Cohen <[email protected]>
---
 sound/core/compress_offload.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c
index fd63d219bf86..XXXXXXXXXXXX 100644
--- a/sound/core/compress_offload.c
+++ b/sound/core/compress_offload.c
@@ -478,11 +478,8 @@ static __poll_t snd_compr_poll(struct file *f, poll_table *wait)
 	/* check if we have at least one fragment to fill */
 	switch (runtime->state) {
 	case SNDRV_PCM_STATE_DRAINING:
-		/* stream has been woken up after drain is complete
-		 * draining done so set stream state to stopped
-		 */
+		/* drain completed or completing, report ready */
 		retval = snd_compr_get_poll(stream);
-		runtime->state = SNDRV_PCM_STATE_SETUP;
 		break;
 	case SNDRV_PCM_STATE_RUNNING:
 	case SNDRV_PCM_STATE_PREPARED:
--
2.43.0