[PATCH 0/6] ALSA: sound/core: fix multiple data races and bugs

Omer Cohen <[email protected]> Fri, 26 Jun 2026 16:47:03 +0300
Newsgroups org.alsa-project.alsa-devel
Message-ID <[email protected]>
To: Takashi Iwai <[email protected]>, Mark Brown <[email protected]>
Cc: [email protected]
Cc: [email protected]

Hi Takashi, Mark,

This is my first patch series to the Linux kernel.  I am CCing the
security team as recommended by the docs for initial reports.

This series fixes 6 bugs found via code review and KCSAN dynamic
analysis in the ALSA sound/core subsystem.  All patches are against
current HEAD (4edcdefd4083).

Patch 1 is confirmed with KCSAN.  The remaining patches address bugs
found during analysis of the same code paths.

This work was AI-assisted.  Per the kernel documentation on reporting
security bugs, it is sent to the public mailing list.  All
reproducers have been tested on arm64 QEMU.

Summary:

  [1/6] ALSA: compress: remove illegal state mutation in poll()
    snd_compr_poll() writes runtime->state = SETUP when it sees
    DRAINING.  This races with snd_compress_wait_for_drain() which
    reads runtime->state concurrently.  KCSAN confirms:

      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
       do_sys_poll+0x2b0/0x628

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

      value changed: 0x00000005 -> 0x00000001

    poll() is a query operation and must not mutate state.  The
    DRAINING->SETUP transition is already handled by drivers via
    snd_compr_drain_notify().

  [2/6] ALSA: compress: fix buffer leak on set_params driver failure
    If ops->set_params() fails after buffer allocation, the buffer
    leaks on retry because runtime->buffer was already overwritten.

  [3/6] ALSA: timer: fix lockless tu->tread read in read()
    snd_timer_user_read() reads tu->tread without ioctl_lock to
    determine entry size, then re-reads it under lock for the copy
    format.  A concurrent TREAD ioctl can change the format between
    reads, causing a size/format mismatch.

  [4/6] ALSA: timer: copy queue entries under lock before copy_to_user
    After dequeuing under qlock, read() drops the lock and calls
    copy_to_user() directly from the queue slot.  Timer callbacks
    can overwrite that slot via queue wraparound during the copy.
    Same bug class as CVE-2017-1000380.

  [5/6] ALSA: pcm: use locked state read in snd_pcm_drop()
    snd_pcm_drop() reads runtime->state without the stream lock.
    Commit 7bc02ab446d3 fixed this pattern in read/write paths
    but missed drop().

  [6/6] ALSA: rawmidi: propagate resize_runtime_buffer() error
    snd_rawmidi_input_params() ignores the return value from
    resize_runtime_buffer() and unconditionally returns 0.

KCSAN reproducer for patch 1:

  The race is triggered by concurrent poll() and DRAIN ioctl on a
  compress offload stream.  A minimal reproducer uses four threads:

    Thread 1: DRAIN ioctl loop
    Thread 2: PARTIAL_DRAIN ioctl loop
    Thread 3: STOP ioctl loop (random delay)
    Thread 4: poll() loop

  The main thread repeatedly sets up and starts the stream.  With
  CONFIG_KCSAN=y and CONFIG_KCSAN_STRICT=y, the race is flagged
  within ~2000 iterations.

  Any compress offload driver (hardware or virtual) exposes the
  race since the bug is in the framework, not driver code.

  Tested on arm64 QEMU, kernel 7.1.0-rc7+ (4edcdefd4083).

Thanks,
Omer